From 9cf18d6099c0b2c51a19ba124088653b9a47eca2 Mon Sep 17 00:00:00 2001 From: Fabio Capucci Date: Thu, 31 Aug 2023 12:56:24 +0200 Subject: [PATCH 1/2] added template factory --- composer.json | 4 +- performance/BenchmarkRunner.php | 6 +- performance/CompiledThemeTestTemplate.php | 6 +- performance/Shopify/CommentFormTag.php | 5 +- performance/Shopify/Database.php | 2 +- performance/Shopify/PaginateTag.php | 22 +++--- performance/ThemeRunner.php | 18 ++--- performance/ThemeTestTemplate.php | 7 +- performance/benchmark.php | 23 +++--- src/Liquid.php | 7 -- src/Parse/BlockParser.php | 7 +- src/Parse/ParseContext.php | 21 ++++-- src/Render/Context.php | 2 +- src/Support/TagRegistry.php | 8 +++ src/Tags/LiquidTag.php | 1 - src/Template.php | 68 +----------------- src/TemplateFactory.php | 71 +++++++++++++++++++ tests/Integration/BlankTest.php | 11 ++- tests/Integration/BlockTest.php | 16 +++-- tests/Integration/ContextTest.php | 2 +- tests/Integration/ErrorHandlingTest.php | 19 +++-- tests/Integration/FilterTest.php | 11 ++- tests/Integration/OutputTest.php | 19 +++-- tests/Integration/ProfilerTest.php | 16 ++--- tests/Integration/Tags/AssignTagTest.php | 7 +- tests/Integration/Tags/CaptureTagTest.php | 3 +- .../Tags/DisableableCustomTagTest.php | 21 ++---- tests/Integration/Tags/ForTagTest.php | 3 +- tests/Integration/Tags/RenderTagTest.php | 7 +- tests/Integration/TemplateTest.php | 37 +++++----- tests/Pest.php | 19 ++++- tests/Unit/BlockTest.php | 13 ++-- tests/Unit/ParseTreeVisitorTest.php | 3 +- tests/Unit/Tags/CaseTagTest.php | 3 +- tests/Unit/Tags/ForTagTest.php | 5 +- tests/Unit/Tags/IfTagTest.php | 3 +- tests/Unit/TemplateText.php | 4 +- tests/Unit/TokenizerTest.php | 2 +- 38 files changed, 264 insertions(+), 238 deletions(-) delete mode 100755 src/Liquid.php create mode 100644 src/TemplateFactory.php diff --git a/composer.json b/composer.json index cd5afe5..c6b4a97 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,6 @@ "symfony/yaml": "^6.0" }, "require-dev": { - "illuminate/support": "^10.21", "laravel/pint": "^1.2", "pestphp/pest": "^2.6", "pestphp/pest-plugin-arch": "^2.0", @@ -46,7 +45,8 @@ "test": "vendor/bin/pest", "test-coverage": "vendor/bin/pest --coverage", "format": "vendor/bin/pint", - "lint": "vendor/bin/phpstan analyse" + "lint": "vendor/bin/phpstan analyse", + "benchmark": "@php performance/benchmark.php" }, "config": { "sort-packages": true, diff --git a/performance/BenchmarkRunner.php b/performance/BenchmarkRunner.php index ca7881a..ad516f4 100644 --- a/performance/BenchmarkRunner.php +++ b/performance/BenchmarkRunner.php @@ -4,15 +4,15 @@ class BenchmarkRunner { - public function run(int $seconds, \Closure $callback) + public function run(int $seconds, \Closure $callback): BenchmarkResult { $durationNs = $seconds * 1e9; $start = hrtime(true); - gc_collect_cycles(); - $runs = []; do { + gc_collect_cycles(); + $runs[] = $this->measure($callback); $end = hrtime(true); } while (($end - $start) < $durationNs); diff --git a/performance/CompiledThemeTestTemplate.php b/performance/CompiledThemeTestTemplate.php index 6234be3..0585ba5 100644 --- a/performance/CompiledThemeTestTemplate.php +++ b/performance/CompiledThemeTestTemplate.php @@ -22,13 +22,13 @@ public function pageTemplate(): string public function render(array $assigns = []): void { + $content = $this->template->render($this->buildContext($assigns)); + if ($this->layout) { $this->layout->render($this->buildContext([ ...$assigns, - 'content_for_layout' => $this->template->render($this->buildContext($assigns)), + 'content_for_layout' => $content, ])); - } else { - $this->template->render($this->buildContext($assigns)); } } diff --git a/performance/Shopify/CommentFormTag.php b/performance/Shopify/CommentFormTag.php index 2e97d60..69719d8 100644 --- a/performance/Shopify/CommentFormTag.php +++ b/performance/Shopify/CommentFormTag.php @@ -38,6 +38,7 @@ public function parse(Tokenizer $tokenizer): static public function render(Context $context): string { $article = $context->get($this->variableName); + assert(is_array($article)); $context->stack(function (Context $context) { $context->set('form', [ @@ -52,10 +53,10 @@ public function render(Context $context): string return $this->wrapInForm($article, parent::render($context)); } - protected function wrapInForm(mixed $article, string $input): string + protected function wrapInForm(array $article, string $input): string { return << +
$input
HTML; diff --git a/performance/Shopify/Database.php b/performance/Shopify/Database.php index 7e9ab6e..5f9ba9f 100644 --- a/performance/Shopify/Database.php +++ b/performance/Shopify/Database.php @@ -11,7 +11,7 @@ class Database protected static ?array $tables = null; - public static function tables() + public static function tables(): array { if (static::$tables !== null) { return static::$tables; diff --git a/performance/Shopify/PaginateTag.php b/performance/Shopify/PaginateTag.php index dee89d5..d02e734 100644 --- a/performance/Shopify/PaginateTag.php +++ b/performance/Shopify/PaginateTag.php @@ -4,6 +4,7 @@ use Keepsuit\Liquid\Exceptions\InvalidArgumentException; use Keepsuit\Liquid\Exceptions\SyntaxException; +use Keepsuit\Liquid\Nodes\Range; use Keepsuit\Liquid\Parse\Regex; use Keepsuit\Liquid\Parse\Tokenizer; use Keepsuit\Liquid\Render\Context; @@ -50,8 +51,11 @@ public function render(Context $context): string $currentPage = $context->get('current_page'); $collection = $context->get($this->collectionName); - $collection = is_iterable($collection) ? iterator_to_array($collection) : $collection; - + $collection = match (true) { + $collection instanceof Range => $collection->toArray(), + $collection instanceof \Iterator => iterator_to_array($collection), + default => $collection, + }; if (! is_array($collection)) { throw new InvalidArgumentException(sprintf('Cannot paginate array %s. Not found.', $this->collectionName)); } @@ -71,14 +75,14 @@ public function render(Context $context): string ]; if ($pageCount > 2) { - foreach (range(1, $pageCount - 1) as $page) { + foreach (range(1, (int) $pageCount - 1) as $page) { $pagination['parts'][] = match (true) { - $currentPage === $page => $this->noLink($page), - $page === 1 => $this->link($page, $page), - $page === $pageCount - 1 => $this->link($page, $page), - $page <= $currentPage - $this->attributes['window_size'] => $this->link('...', $page), - $page >= $currentPage + $this->attributes['window_size'] => $this->link('...', $page), - default => $this->link($page, $page), + $currentPage === $page => $this->noLink((string) $page), + $page === 1 => $this->link((string) $page, $page), + $page === $pageCount - 1 => $this->link((string) $page, $page), + $page <= ($currentPage - (int) $this->attributes['window_size']) => $this->link('...', $page), + $page >= ($currentPage + (int) $this->attributes['window_size']) => $this->link('...', $page), + default => $this->link((string) $page, $page), }; } } diff --git a/performance/ThemeRunner.php b/performance/ThemeRunner.php index f587308..1bc2e6d 100644 --- a/performance/ThemeRunner.php +++ b/performance/ThemeRunner.php @@ -4,7 +4,7 @@ use Keepsuit\Liquid\Performance\Shopify\Database; use Keepsuit\Liquid\Support\Arr; -use Keepsuit\Liquid\Template; +use Keepsuit\Liquid\TemplateFactory; class ThemeRunner { @@ -18,9 +18,10 @@ class ThemeRunner */ protected array $compiledTemplates; - public function __construct() - { - $files = glob(__DIR__.'/tests/**/*.liquid'); + public function __construct( + protected TemplateFactory $templateFactory + ) { + $files = glob(__DIR__.'/tests/**/*.liquid') ?: []; $this->tests = Arr::compact(Arr::map($files, function (string $path) { if (basename($path) === 'theme.liquid') { @@ -30,9 +31,10 @@ public function __construct() $themePath = dirname($path).'/theme.liquid'; return new ThemeTestTemplate( + factory: $this->templateFactory, templateName: $path, - liquid: file_get_contents($path), - layoutLiquid: file_exists($themePath) ? file_get_contents($themePath) : null, + liquid: file_get_contents($path) ?: '', + layoutLiquid: file_exists($themePath) ? (file_get_contents($themePath) ?: '') : null, ); })); @@ -42,9 +44,9 @@ public function __construct() public function compile(): void { foreach ($this->tests as $test) { - Template::parse($test->liquid); + $this->templateFactory->parse($test->liquid); if ($test->layoutLiquid !== null) { - Template::parse($test->layoutLiquid); + $this->templateFactory->parse($test->layoutLiquid); } } } diff --git a/performance/ThemeTestTemplate.php b/performance/ThemeTestTemplate.php index 63794c2..7904d11 100644 --- a/performance/ThemeTestTemplate.php +++ b/performance/ThemeTestTemplate.php @@ -2,11 +2,12 @@ namespace Keepsuit\Liquid\Performance; -use Keepsuit\Liquid\Template; +use Keepsuit\Liquid\TemplateFactory; class ThemeTestTemplate { public function __construct( + protected TemplateFactory $factory, public string $templateName, public string $liquid, public ?string $layoutLiquid, @@ -20,8 +21,8 @@ public function pageTemplate(): string public function compile(): CompiledThemeTestTemplate { - $template = Template::parse($this->liquid); - $layout = $this->layoutLiquid !== null ? Template::parse($this->layoutLiquid) : null; + $template = $this->factory->parse($this->liquid); + $layout = $this->layoutLiquid !== null ? $this->factory->parse($this->layoutLiquid) : null; return new CompiledThemeTestTemplate( templateName: $this->templateName, diff --git a/performance/benchmark.php b/performance/benchmark.php index 097a079..e4fe350 100644 --- a/performance/benchmark.php +++ b/performance/benchmark.php @@ -6,7 +6,7 @@ use Keepsuit\Liquid\Performance\Shopify\CommentFormTag; use Keepsuit\Liquid\Performance\Shopify\PaginateTag; use Keepsuit\Liquid\Performance\ThemeRunner; -use Keepsuit\Liquid\Template; +use Keepsuit\Liquid\TemplateFactory; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -15,28 +15,32 @@ require __DIR__.'/../vendor/autoload.php'; -Template::registerTag(CommentFormTag::class); -Template::registerTag(PaginateTag::class); +$templateFactory = TemplateFactory::new() + ->registerTag(CommentFormTag::class) + ->registerTag(PaginateTag::class); (new SingleCommandApplication()) ->setName('Benchmark') - ->setCode(function (InputInterface $input, OutputInterface $output) { + ->setCode(function (InputInterface $input, OutputInterface $output) use ($templateFactory) { $style = new SymfonyStyle($input, $output); $times = 10; - $warmup = 5; + $warmup = 0; - $output->writeln(sprintf('Running benchmark for %s seconds (with %s seconds warmup).', $times, $warmup)); + if ($warmup > 0) { + $output->writeln(sprintf('Running benchmark for %s seconds (with %s seconds warmup).', $times, $warmup)); + } else { + $output->writeln(sprintf('Running benchmark for %s seconds.', $times)); + } $benchmark = new BenchmarkRunner(); - $profiler = new ThemeRunner(); + $profiler = new ThemeRunner($templateFactory); if ($warmup > 0) { $output->writeln('Warming up...'); $benchmark->run($warmup, fn () => $profiler->compile()); } - $output->writeln('Benchmarking...'); $computeTable = $style->createTable(); $computeTable->setHeaders([ 'test', @@ -45,8 +49,11 @@ 'runs', 'duration', ]); + $output->writeln('Running parse benchmark...'); outputBenchmarkResult($computeTable, 'parse', $benchmark->run($times, fn () => $profiler->compile())); + $output->writeln('Running render benchmark...'); outputBenchmarkResult($computeTable, 'render', $benchmark->run($times, fn () => $profiler->render())); + $output->writeln('Running parse & render benchmark...'); outputBenchmarkResult($computeTable, 'parse & render', $benchmark->run($times, fn () => $profiler->run())); $computeTable->render(); }) diff --git a/src/Liquid.php b/src/Liquid.php deleted file mode 100755 index f275993..0000000 --- a/src/Liquid.php +++ /dev/null @@ -1,7 +0,0 @@ -|null $tagClass */ - $tagClass = Template::registeredTags()[$tagName] ?? null; + $tagClass = $parseContext->tagRegistry->get($tagName) ?? null; if ($tagClass !== null) { $tag = (new $tagClass($markup, $parseContext)); @@ -182,7 +181,7 @@ protected function parseForLiquidTag(Tokenizer $tokenizer, ParseContext $parseCo $markup = $matches[2]; /** @var class-string|null $tagClass */ - $tagClass = Template::registeredTags()[$tagName] ?? null; + $tagClass = $parseContext->tagRegistry->get($tagName) ?? null; if ($tagClass !== null) { $tag = (new $tagClass($markup, $parseContext))->parse($tokenizer); diff --git a/src/Parse/ParseContext.php b/src/Parse/ParseContext.php index 9edffd0..e1984ab 100644 --- a/src/Parse/ParseContext.php +++ b/src/Parse/ParseContext.php @@ -5,12 +5,11 @@ use Closure; use Keepsuit\Liquid\Exceptions\SyntaxException; use Keepsuit\Liquid\Support\I18n; +use Keepsuit\Liquid\Support\TagRegistry; use Keepsuit\Liquid\Template; class ParseContext { - public readonly I18n $locale; - public ?int $lineNumber = null; public bool $trimWhitespace = false; @@ -25,14 +24,20 @@ class ParseContext protected array $warnings = []; public function __construct( - I18n $locale = null, + bool|int $startLineNumber = null, + public readonly TagRegistry $tagRegistry = new TagRegistry(), + public readonly I18n $locale = new I18n(), ) { - $this->locale = $locale ?? new I18n(); + $this->lineNumber = match (true) { + is_int($startLineNumber) => $startLineNumber, + $startLineNumber === true => 1, + default => null, + }; } - public function newTokenizer(string $markup, int $startLineNumber = null, bool $forLiquidTag = false): Tokenizer + public function newTokenizer(string $markup, bool $forLiquidTag = false): Tokenizer { - return new Tokenizer($markup, startLineNumber: $startLineNumber, forLiquidTag: $forLiquidTag); + return new Tokenizer($markup, startLineNumber: $this->lineNumber, forLiquidTag: $forLiquidTag); } public function parseExpression(string $markup): mixed @@ -53,12 +58,16 @@ public function logWarning(SyntaxException $e): void */ public function partial(Closure $closure) { + $oldLineNumber = $this->lineNumber; + $this->partial = true; + $this->lineNumber = $this->lineNumber !== null ? 1 : null; try { return $closure($this); } finally { $this->partial = false; + $this->lineNumber = $oldLineNumber; } } } diff --git a/src/Render/Context.php b/src/Render/Context.php index 7f4ae99..88f20a3 100644 --- a/src/Render/Context.php +++ b/src/Render/Context.php @@ -324,7 +324,7 @@ public function loadPartial(ParseContext $parseContext, string $templateName): T try { $template = $parseContext->partial(function (ParseContext $parseContext) use ($templateName, $source) { - return Template::parsePartial($source, $parseContext, $templateName); + return Template::parse($parseContext, $source, $templateName); }); } catch (LiquidException $exception) { $exception->templateName = $templateName; diff --git a/src/Support/TagRegistry.php b/src/Support/TagRegistry.php index d40ce64..e5cc190 100644 --- a/src/Support/TagRegistry.php +++ b/src/Support/TagRegistry.php @@ -29,6 +29,14 @@ public function delete(string $name): static return $this; } + /** + * @return class-string|null + */ + public function get(string $name): ?string + { + return $this->tags[$name] ?? null; + } + /** * @return array> */ diff --git a/src/Tags/LiquidTag.php b/src/Tags/LiquidTag.php index 3d57bd2..7f327a3 100644 --- a/src/Tags/LiquidTag.php +++ b/src/Tags/LiquidTag.php @@ -24,7 +24,6 @@ public function parse(Tokenizer $tokenizer): static { $liquidTokenizer = $this->parseContext->newTokenizer( markup: $this->markup, - startLineNumber: $this->lineNumber, forLiquidTag: true ); diff --git a/src/Template.php b/src/Template.php index 5c2137c..c8b6961 100644 --- a/src/Template.php +++ b/src/Template.php @@ -7,12 +7,9 @@ use Keepsuit\Liquid\Parse\ParseContext; use Keepsuit\Liquid\Profiler\Profiler; use Keepsuit\Liquid\Render\Context; -use Keepsuit\Liquid\Support\TagRegistry; class Template { - protected static TagRegistry $tagRegistry; - /** * @var array<\Throwable> */ @@ -29,21 +26,9 @@ public function __construct( /** * @throws SyntaxException */ - public static function parse( - string $source, - bool $lineNumbers = false, - ): Template { - $parseContext = new ParseContext(); - $tokenizer = $parseContext->newTokenizer($source, startLineNumber: $lineNumbers ? 1 : null); - - return new Template( - root: Document::parse($tokenizer, $parseContext) - ); - } - - public static function parsePartial(string $source, ParseContext $parseContext, string $name = null): Template + public static function parse(ParseContext $parseContext, string $source, string $name = null): Template { - $tokenizer = $parseContext->newTokenizer($source, startLineNumber: $parseContext->lineNumber !== null ? 1 : null); + $tokenizer = $parseContext->newTokenizer($source); return new Template( root: Document::parse($tokenizer, $parseContext), @@ -62,55 +47,6 @@ public function render(Context $context = new Context()): string } } - /** - * @return array> - */ - public static function registeredTags(): array - { - return static::getTagRegistry()->all(); - } - - /** - * @param class-string $tag - */ - public static function registerTag(string $tag): void - { - static::getTagRegistry()->register($tag); - } - - protected static function getTagRegistry(): TagRegistry - { - if (isset(static::$tagRegistry)) { - return static::$tagRegistry; - } - - return static::$tagRegistry = (new TagRegistry()) - ->register(Tags\AssignTag::class) - ->register(Tags\BreakTag::class) - ->register(Tags\CaptureTag::class) - ->register(Tags\CaseTag::class) - ->register(Tags\CommentTag::class) - ->register(Tags\ContinueTag::class) - ->register(Tags\CycleTag::class) - ->register(Tags\DecrementTag::class) - ->register(Tags\EchoTag::class) - ->register(Tags\ForTag::class) - ->register(Tags\IfChanged::class) - ->register(Tags\IfTag::class) - ->register(Tags\IncrementTag::class) - ->register(Tags\InlineCommentTag::class) - ->register(Tags\LiquidTag::class) - ->register(Tags\RawTag::class) - ->register(Tags\RenderTag::class) - ->register(Tags\TableRowTag::class) - ->register(Tags\UnlessTag::class); - } - - public static function deleteTag(string $name): void - { - static::getTagRegistry()->delete($name); - } - public function getErrors(): array { return $this->errors; diff --git a/src/TemplateFactory.php b/src/TemplateFactory.php new file mode 100644 index 0000000..17a9f3d --- /dev/null +++ b/src/TemplateFactory.php @@ -0,0 +1,71 @@ +tagRegistry = $this->buildTagRegistry(); + } + + public static function new(): TemplateFactory + { + return new self(); + } + + /** + * @throws SyntaxException + */ + public function parse( + string $source, + bool $lineNumbers = false, + ): Template { + $parseContext = new ParseContext( + startLineNumber: $lineNumbers, + tagRegistry: $this->tagRegistry + ); + + return Template::parse($parseContext, $source); + } + + protected function buildTagRegistry(): TagRegistry + { + return (new TagRegistry()) + ->register(Tags\AssignTag::class) + ->register(Tags\BreakTag::class) + ->register(Tags\CaptureTag::class) + ->register(Tags\CaseTag::class) + ->register(Tags\CommentTag::class) + ->register(Tags\ContinueTag::class) + ->register(Tags\CycleTag::class) + ->register(Tags\DecrementTag::class) + ->register(Tags\EchoTag::class) + ->register(Tags\ForTag::class) + ->register(Tags\IfChanged::class) + ->register(Tags\IfTag::class) + ->register(Tags\IncrementTag::class) + ->register(Tags\InlineCommentTag::class) + ->register(Tags\LiquidTag::class) + ->register(Tags\RawTag::class) + ->register(Tags\RenderTag::class) + ->register(Tags\TableRowTag::class) + ->register(Tags\UnlessTag::class); + } + + /** + * @param class-string $tag + */ + public function registerTag(string $tag): TemplateFactory + { + $this->tagRegistry->register($tag); + + return $this; + } +} diff --git a/tests/Integration/BlankTest.php b/tests/Integration/BlankTest.php index 9d9c792..783f230 100644 --- a/tests/Integration/BlankTest.php +++ b/tests/Integration/BlankTest.php @@ -1,16 +1,13 @@ registerTag(FooBarTag::class); - expect(renderTemplate(wrapInFor('{% foobar %}')))->toBe(str_repeat(' ', 10)); + expect(renderTemplate(wrapInFor('{% foobar %}'), factory: $factory)) + ->toBe(str_repeat(' ', 10)); }); test('loops are blank', function () { diff --git a/tests/Integration/BlockTest.php b/tests/Integration/BlockTest.php index 5873854..e3c7a98 100644 --- a/tests/Integration/BlockTest.php +++ b/tests/Integration/BlockTest.php @@ -1,7 +1,10 @@ templateFactory = \Keepsuit\Liquid\TemplateFactory::new(); +}); test('unexpected end tag', function () { expect(fn () => renderTemplate('{% if true %}{% endunless %}')) @@ -9,18 +12,21 @@ }); test('with custom tag block', function () { - Template::registerTag(\Keepsuit\Liquid\Tests\Stubs\TestTagBlockTag::class); + $this->templateFactory->registerTag(\Keepsuit\Liquid\Tests\Stubs\TestTagBlockTag::class); + assertTemplateResult( '', - '{% testblock %}{% endtestblock %}' + '{% testblock %}{% endtestblock %}', + factory: $this->templateFactory ); }); test('custom tag block have a default render method', function () { - Template::registerTag(\Keepsuit\Liquid\Tests\Stubs\TestTagBlockTag::class); + $this->templateFactory->registerTag(\Keepsuit\Liquid\Tests\Stubs\TestTagBlockTag::class); assertTemplateResult( ' bla ', - '{% testblock %} bla {% endtestblock %}' + '{% testblock %} bla {% endtestblock %}', + factory: $this->templateFactory ); }); diff --git a/tests/Integration/ContextTest.php b/tests/Integration/ContextTest.php index 6995245..0c8ff5c 100644 --- a/tests/Integration/ContextTest.php +++ b/tests/Integration/ContextTest.php @@ -437,7 +437,7 @@ function () use (&$global) { $context = new Context(filters: [\Keepsuit\Liquid\Tests\Stubs\TestFilters::class]); $subContext = $context->newIsolatedSubContext('sub'); - expect(\Keepsuit\Liquid\Template::parse('{{ "hi?" | hi }}')->render($subContext))->toBe('hi? hi!'); + expect(parseTemplate('{{ "hi?" | hi }}')->render($subContext))->toBe('hi? hi!'); }); test('disabled specified tags', function () { diff --git a/tests/Integration/ErrorHandlingTest.php b/tests/Integration/ErrorHandlingTest.php index 0fa8f69..3cfe75d 100644 --- a/tests/Integration/ErrorHandlingTest.php +++ b/tests/Integration/ErrorHandlingTest.php @@ -4,7 +4,6 @@ use Keepsuit\Liquid\Exceptions\StandardException; use Keepsuit\Liquid\Exceptions\SyntaxException; use Keepsuit\Liquid\Render\Context; -use Keepsuit\Liquid\Template; use Keepsuit\Liquid\Tests\Stubs\ErrorDrop; use Keepsuit\Liquid\Tests\Stubs\StubFileSystem; @@ -41,7 +40,7 @@ }); test('standard error', function () { - $template = Template::parse(' {{ errors.standard_error }} '); + $template = parseTemplate(' {{ errors.standard_error }} ', lineNumbers: false); expect($template->render(new Context(staticEnvironment: ['errors' => new ErrorDrop()]))) ->toBe(' Liquid error: Standard error '); @@ -51,7 +50,7 @@ }); test('syntax error', function () { - $template = Template::parse(' {{ errors.syntax_error }} '); + $template = parseTemplate(' {{ errors.syntax_error }} ', lineNumbers: false); expect($template->render(new Context(staticEnvironment: ['errors' => new ErrorDrop()]))) ->toBe(' Liquid syntax error: Syntax error '); @@ -61,7 +60,7 @@ }); test('argument error', function () { - $template = Template::parse(' {{ errors.argument_error }} '); + $template = parseTemplate(' {{ errors.argument_error }} ', lineNumbers: false); expect($template->render(new Context(staticEnvironment: ['errors' => new ErrorDrop()]))) ->toBe(' Liquid error: Argument error '); @@ -78,7 +77,7 @@ }); test('unrecognized operator', function () { - expect(fn () => Template::parse('{% if 1 =! 2 %}ok{% endif %}'))->toThrow(SyntaxException::class); + expect(fn () => parseTemplate('{% if 1 =! 2 %}ok{% endif %}'))->toThrow(SyntaxException::class); }); test('with line numbers adds numbers to parser errors', function () { @@ -109,7 +108,7 @@ test('parsing strict with line numbers adds numbers to lexer errors', function () { try { - Template::parse( + parseTemplate( <<<'LIQUID' foobar @@ -159,7 +158,7 @@ }); test('default exception renderer with internal error', function () { - $template = Template::parse('This is a runtime error: {{ errors.runtime_error }}', lineNumbers: true); + $template = parseTemplate('This is a runtime error: {{ errors.runtime_error }}', lineNumbers: true); $output = $template->render(new Context(staticEnvironment: ['errors' => new ErrorDrop()])); @@ -170,7 +169,7 @@ }); test('render template name with line numbers', function () { - $template = Template::parse("Argument error:\n{% render 'product' with errors %}", lineNumbers: true); + $template = parseTemplate("Argument error:\n{% render 'product' with errors %}", lineNumbers: true); $output = $template->render(new Context( staticEnvironment: ['errors' => new ErrorDrop()], @@ -188,7 +187,7 @@ }); test('error is thrown during parse with template name', function () { - $template = Template::parse("{% render 'loop' %}", lineNumbers: true); + $template = parseTemplate("{% render 'loop' %}", lineNumbers: true); $output = $template->render(new Context( staticEnvironment: ['errors' => new ErrorDrop()], @@ -201,7 +200,7 @@ }); test('internal error is thrown with template name', function () { - $template = Template::parse("{% render 'snippet' with errors %}", lineNumbers: true); + $template = parseTemplate("{% render 'snippet' with errors %}", lineNumbers: true); $output = $template->render(new Context( staticEnvironment: ['errors' => new ErrorDrop()], diff --git a/tests/Integration/FilterTest.php b/tests/Integration/FilterTest.php index 5e3dbf1..18ca865 100644 --- a/tests/Integration/FilterTest.php +++ b/tests/Integration/FilterTest.php @@ -1,7 +1,6 @@ set('var', 1000); - expect(Template::parse('{{ var | money }}')->render($context)) + expect(parseTemplate('{{ var | money }}')->render($context)) ->toBe(' 1000$'); }); @@ -24,7 +23,7 @@ ); $context->set('var', 1000); - expect(Template::parse('{{ var | money_with_underscore }}')->render($context)) + expect(parseTemplate('{{ var | money_with_underscore }}')->render($context)) ->toBe(' 1000$'); }); @@ -34,7 +33,7 @@ ); $context->set('var', 1000); - expect(Template::parse('{{ var | money }}')->render($context)) + expect(parseTemplate('{{ var | money }}')->render($context)) ->toBe(' 1000$ CAD'); }); @@ -126,7 +125,7 @@ $context->set('surname', 'john'); $context->set('input', 'hello %{first_name}, %{last_name}'); - expect(Template::parse("{{ input | substitute: first_name: surname, last_name: 'doe' }}")->render($context)) + expect(parseTemplate("{{ input | substitute: first_name: surname, last_name: 'doe' }}")->render($context)) ->toBe('hello john, doe'); }); @@ -135,6 +134,6 @@ filters: [HtmlAttributesFilter::class] ); - expect(Template::parse("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}")->render($context)) + expect(parseTemplate("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}")->render($context)) ->toBe("data-src='src' data-widths='100, 200'"); }); diff --git a/tests/Integration/OutputTest.php b/tests/Integration/OutputTest.php index 3d48673..10ec09a 100644 --- a/tests/Integration/OutputTest.php +++ b/tests/Integration/OutputTest.php @@ -1,7 +1,6 @@ render($context)) + expect(parseTemplate(' {{ car.gm | make_funny }} ')->render($context)) ->toBe(' LOL '); }); @@ -48,7 +47,7 @@ filters: [FunnyFilter::class] ); - expect(Template::parse(' {{ car.gm | cite_funny }} ')->render($context)) + expect(parseTemplate(' {{ car.gm | cite_funny }} ')->render($context)) ->toBe(' LOL: bad '); }); @@ -58,7 +57,7 @@ filters: [FunnyFilter::class] ); - expect(Template::parse(" {{ car.gm | add_smiley : ':-(' }} ")->render($context)) + expect(parseTemplate(" {{ car.gm | add_smiley : ':-(' }} ")->render($context)) ->toBe(' bad :-( '); }); @@ -68,7 +67,7 @@ filters: [FunnyFilter::class] ); - expect(Template::parse(' {{ car.gm | add_smiley }} ')->render($context)) + expect(parseTemplate(' {{ car.gm | add_smiley }} ')->render($context)) ->toBe(' bad :-) '); }); @@ -78,7 +77,7 @@ filters: [FunnyFilter::class] ); - expect(Template::parse(" {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} ")->render($context)) + expect(parseTemplate(" {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} ")->render($context)) ->toBe(' bad :-( :-( '); }); @@ -88,7 +87,7 @@ filters: [FunnyFilter::class] ); - expect(Template::parse(" {{ car.gm | add_tag : 'span', 'bar'}} ")->render($context)) + expect(parseTemplate(" {{ car.gm | add_tag : 'span', 'bar'}} ")->render($context)) ->toBe(' bad '); }); @@ -98,7 +97,7 @@ filters: [FunnyFilter::class] ); - expect(Template::parse(" {{ car.gm | add_tag : 'span', car.bmw}} ")->render($context)) + expect(parseTemplate(" {{ car.gm | add_tag : 'span', car.bmw}} ")->render($context)) ->toBe(' bad '); }); @@ -108,7 +107,7 @@ filters: [FunnyFilter::class] ); - expect(Template::parse(' {{ best_cars | cite_funny | paragraph }} ')->render($context)) + expect(parseTemplate(' {{ best_cars | cite_funny | paragraph }} ')->render($context)) ->toBe('

LOL: bmw

'); }); @@ -118,6 +117,6 @@ filters: [FunnyFilter::class] ); - expect(Template::parse(" {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} ")->render($context)) + expect(parseTemplate(" {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} ")->render($context)) ->toBe(' Typo '); }); diff --git a/tests/Integration/ProfilerTest.php b/tests/Integration/ProfilerTest.php index 645294f..63c03d8 100644 --- a/tests/Integration/ProfilerTest.php +++ b/tests/Integration/ProfilerTest.php @@ -4,19 +4,17 @@ use Keepsuit\Liquid\Profiler\Timing; use Keepsuit\Liquid\Render\Context; use Keepsuit\Liquid\Support\Arr; -use Keepsuit\Liquid\Template; +use Keepsuit\Liquid\TemplateFactory; use Keepsuit\Liquid\Tests\Stubs\ProfilingFileSystem; +use Keepsuit\Liquid\Tests\Stubs\SleepTag; beforeEach(function () { - Template::registerTag(\Keepsuit\Liquid\Tests\Stubs\SleepTag::class); -}); - -afterEach(function () { - Template::deleteTag(\Keepsuit\Liquid\Tests\Stubs\SleepTag::class); + $this->templateFactory = TemplateFactory::new() + ->registerTag(SleepTag::class); }); test('context allows flagging profiling', function () { - $template = Template::parse("{{ 'a string' | upcase }}"); + $template = parseTemplate("{{ 'a string' | upcase }}"); $template->render(new Context()); expect($template->getProfiler())->toBeNull(); @@ -82,7 +80,7 @@ test('profiling multiple renders', function () { $context = new Context(profile: true, fileSystem: new ProfilingFileSystem()); - $template = Template::parse('{% sleep 0.001 %}', lineNumbers: true); + $template = parseTemplate('{% sleep 0.001 %}', lineNumbers: true, factory: $this->templateFactory); invade($context)->templateName = 'index'; $template->render($context); @@ -188,7 +186,7 @@ function profileTemplate(string $source, array $assigns = []): ?Profiler { - $template = Template::parse($source, lineNumbers: true); + $template = test()->templateFactory->parse($source, lineNumbers: true); $template->render(new Context( staticEnvironment: $assigns, profile: true, diff --git a/tests/Integration/Tags/AssignTagTest.php b/tests/Integration/Tags/AssignTagTest.php index f5431b4..239321d 100644 --- a/tests/Integration/Tags/AssignTagTest.php +++ b/tests/Integration/Tags/AssignTagTest.php @@ -4,7 +4,6 @@ use Keepsuit\Liquid\Exceptions\SyntaxException; use Keepsuit\Liquid\Render\Context; use Keepsuit\Liquid\Render\ResourceLimits; -use Keepsuit\Liquid\Template; test('assign with hyphen in variable name', function () { $source = <<<'LIQUID' @@ -54,7 +53,7 @@ }); test('assign score exceeding resource limit', function () { - $template = Template::parse('{% assign foo = 42 %}{% assign bar = 23 %}'); + $template = parseTemplate('{% assign foo = 42 %}{% assign bar = 23 %}'); $context = new Context(rethrowExceptions: true, resourceLimits: new ResourceLimits(assignScoreLimit: 1)); expect(fn () => $template->render($context))->toThrow(ResourceLimitException::class); @@ -67,7 +66,7 @@ }); test('assign score exceeding resource limit from composite object', function () { - $template = Template::parse("{% assign foo = 'aaaa' | split: '' %}"); + $template = parseTemplate("{% assign foo = 'aaaa' | split: '' %}"); $context = new Context(rethrowExceptions: true, resourceLimits: new ResourceLimits(assignScoreLimit: 3)); expect(fn () => $template->render($context))->toThrow(ResourceLimitException::class); @@ -100,7 +99,7 @@ function assignScoreOf(mixed $value): int { $context = new Context(rethrowExceptions: true, staticEnvironment: ['value' => $value]); - Template::parse('{% assign obj = value %}')->render($context); + parseTemplate('{% assign obj = value %}')->render($context); return $context->resourceLimits->getAssignScore(); } diff --git a/tests/Integration/Tags/CaptureTagTest.php b/tests/Integration/Tags/CaptureTagTest.php index 4b8a6e7..c3ec562 100644 --- a/tests/Integration/Tags/CaptureTagTest.php +++ b/tests/Integration/Tags/CaptureTagTest.php @@ -1,7 +1,6 @@ render($context); + parseTemplate('{% capture foo %}すごい{% endcapture %}')->render($context); expect($context->resourceLimits->getAssignScore())->toBe(9); }); diff --git a/tests/Integration/Tags/DisableableCustomTagTest.php b/tests/Integration/Tags/DisableableCustomTagTest.php index 766340c..c9d76b9 100644 --- a/tests/Integration/Tags/DisableableCustomTagTest.php +++ b/tests/Integration/Tags/DisableableCustomTagTest.php @@ -4,31 +4,24 @@ use Keepsuit\Liquid\Render\Context; use Keepsuit\Liquid\Tag; use Keepsuit\Liquid\TagBlock; -use Keepsuit\Liquid\Template; beforeEach(function () { - Template::registerTag(CustomTag::class); - Template::registerTag(Custom2Tag::class); -}); - -afterEach(function () { - Template::deleteTag(CustomTag::class); - Template::deleteTag(Custom2Tag::class); - Template::deleteTag(DisableCustomTag::class); - Template::deleteTag(DisableBothTag::class); + $this->templateFactory = \Keepsuit\Liquid\TemplateFactory::new() + ->registerTag(CustomTag::class) + ->registerTag(Custom2Tag::class); }); test('block tag disabling nested tag', function () { - Template::registerTag(DisableCustomTag::class); + $this->templateFactory->registerTag(DisableCustomTag::class); - expect(renderTemplate('{% disable %}{% custom %};{% custom2 %}{% enddisable %}', renderErrors: true)) + expect(renderTemplate('{% disable %}{% custom %};{% custom2 %}{% enddisable %}', renderErrors: true, factory: $this->templateFactory)) ->toBe('Liquid error (line 1): custom usage is not allowed in this context;custom2'); }); test('block tag disabling multiple tags', function () { - Template::registerTag(DisableBothTag::class); + $this->templateFactory->registerTag(DisableBothTag::class); - expect(renderTemplate('{% disable %}{% custom %};{% custom2 %}{% enddisable %}', renderErrors: true)) + expect(renderTemplate('{% disable %}{% custom %};{% custom2 %}{% enddisable %}', renderErrors: true, factory: $this->templateFactory)) ->toBe('Liquid error (line 1): custom usage is not allowed in this context;Liquid error (line 1): custom2 usage is not allowed in this context'); }); diff --git a/tests/Integration/Tags/ForTagTest.php b/tests/Integration/Tags/ForTagTest.php index f9c15f4..58e84ca 100644 --- a/tests/Integration/Tags/ForTagTest.php +++ b/tests/Integration/Tags/ForTagTest.php @@ -3,7 +3,6 @@ use Keepsuit\Liquid\Exceptions\InvalidArgumentException; use Keepsuit\Liquid\Exceptions\SyntaxException; use Keepsuit\Liquid\Render\Context; -use Keepsuit\Liquid\Template; use Keepsuit\Liquid\Tests\Stubs\ErrorDrop; use Keepsuit\Liquid\Tests\Stubs\LoaderDrop; use Keepsuit\Liquid\Tests\Stubs\ThingWithValue; @@ -368,7 +367,7 @@ test('for cleans up registers', function () { $context = new Context(rethrowExceptions: true, staticEnvironment: ['drop' => new ErrorDrop()]); - expect(fn () => Template::parse('{% for i in (1..2) %}{{ drop.standard_error }}{% endfor %}')->render($context))->toThrow(\Keepsuit\Liquid\Exceptions\StandardException::class); + expect(fn () => parseTemplate('{% for i in (1..2) %}{{ drop.standard_error }}{% endfor %}')->render($context))->toThrow(\Keepsuit\Liquid\Exceptions\StandardException::class); expect($context->getRegister('for_stack'))->toBe([]); }); diff --git a/tests/Integration/Tags/RenderTagTest.php b/tests/Integration/Tags/RenderTagTest.php index 6aaaeb5..0bee330 100644 --- a/tests/Integration/Tags/RenderTagTest.php +++ b/tests/Integration/Tags/RenderTagTest.php @@ -3,7 +3,6 @@ use Keepsuit\Liquid\Exceptions\StackLevelException; use Keepsuit\Liquid\Exceptions\SyntaxException; use Keepsuit\Liquid\Render\Context; -use Keepsuit\Liquid\Template; use Keepsuit\Liquid\Tests\Stubs\StubFileSystem; test('render with no arguments', function () { @@ -83,7 +82,7 @@ fileSystem: $fileSystem ); - expect(Template::parse('{% render "snippet" %}{% render "snippet" %}')->render($context))->toBe('echoecho'); + expect(parseTemplate('{% render "snippet" %}{% render "snippet" %}')->render($context))->toBe('echoecho'); expect($fileSystem->fileReadCount)->toBe(1); }); @@ -93,13 +92,13 @@ $context = new Context( fileSystem: $fileSystem ); - expect(Template::parse('{% render "snippet" %}')->render($context))->toBe('my message'); + expect(parseTemplate('{% render "snippet" %}')->render($context))->toBe('my message'); expect($fileSystem->fileReadCount)->toBe(1); $context = new Context( fileSystem: $fileSystem ); - expect(Template::parse('{% render "snippet" %}')->render($context))->toBe('my message'); + expect(parseTemplate('{% render "snippet" %}')->render($context))->toBe('my message'); expect($fileSystem->fileReadCount)->toBe(2); }); diff --git a/tests/Integration/TemplateTest.php b/tests/Integration/TemplateTest.php index 1fb57af..2cb2184 100644 --- a/tests/Integration/TemplateTest.php +++ b/tests/Integration/TemplateTest.php @@ -6,10 +6,9 @@ use Keepsuit\Liquid\Exceptions\UndefinedVariableException; use Keepsuit\Liquid\Render\Context; use Keepsuit\Liquid\Render\ResourceLimits; -use Keepsuit\Liquid\Template; test('assigns persist on same context between renders', function () { - $template = Template::parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}"); + $template = parseTemplate("{{ foo }}{% assign foo = 'foo' %}{{ foo }}"); $context = new Context(); expect($template->render($context))->toBe('foo'); @@ -17,14 +16,14 @@ }); test('assigns does not persist on different contexts between renders', function () { - $template = Template::parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}"); + $template = parseTemplate("{{ foo }}{% assign foo = 'foo' %}{{ foo }}"); expect($template->render(new Context()))->toBe('foo'); expect($template->render(new Context()))->toBe('foo'); }); test('lamdba is called once over multiple renders', function () { - $template = Template::parse('{{ number }}'); + $template = parseTemplate('{{ number }}'); $global = 0; $context = new Context( @@ -42,7 +41,7 @@ }); test('resource limits render length', function () { - $template = Template::parse('0123456789'); + $template = parseTemplate('0123456789'); $context = new Context( resourceLimits: new ResourceLimits(renderLengthLimit: 9) @@ -58,14 +57,14 @@ }); test('resource limits render score', function () { - $template = Template::parse('{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}'); + $template = parseTemplate('{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}'); $context = new Context( resourceLimits: new ResourceLimits(renderScoreLimit: 50) ); expect(fn () => $template->render($context))->toThrow(ResourceLimitException::class); expect($context->resourceLimits->reached())->toBeTrue(); - $template = Template::parse('{% for a in (1..100) %} foo {% endfor %}'); + $template = parseTemplate('{% for a in (1..100) %} foo {% endfor %}'); $context = new Context( resourceLimits: new ResourceLimits(renderScoreLimit: 50) ); @@ -80,7 +79,7 @@ }); test('resource limits abort rendering after first error', function () { - $template = Template::parse('{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}'); + $template = parseTemplate('{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}'); $context = new Context( rethrowExceptions: false, resourceLimits: new ResourceLimits(renderScoreLimit: 50) @@ -90,7 +89,7 @@ }); test('resource limits get updated even if no limits are set', function () { - $template = Template::parse('{% for a in (1..100) %}x{% assign foo = 1 %} {% endfor %}'); + $template = parseTemplate('{% for a in (1..100) %}x{% assign foo = 1 %} {% endfor %}'); $context = new Context(); $template->render($context); @@ -101,19 +100,19 @@ }); test('render length persists between blocks', function () { - $template = Template::parse('{% if true %}aaaa{% endif %}'); + $template = parseTemplate('{% if true %}aaaa{% endif %}'); $context = new Context(resourceLimits: new ResourceLimits(renderLengthLimit: 3)); expect(fn () => $template->render($context))->toThrow(ResourceLimitException::class); $context = new Context(resourceLimits: new ResourceLimits(renderLengthLimit: 4)); expect($template->render($context))->toBe('aaaa'); - $template = Template::parse('{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}'); + $template = parseTemplate('{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}'); $context = new Context(resourceLimits: new ResourceLimits(renderLengthLimit: 6)); expect(fn () => $template->render($context))->toThrow(ResourceLimitException::class); $context = new Context(resourceLimits: new ResourceLimits(renderLengthLimit: 7)); expect($template->render($context))->toBe('aaaabbb'); - $template = Template::parse('{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}'); + $template = parseTemplate('{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}'); $context = new Context(resourceLimits: new ResourceLimits(renderLengthLimit: 5)); expect(fn () => $template->render($context))->toThrow(ResourceLimitException::class); $context = new Context(resourceLimits: new ResourceLimits(renderLengthLimit: 6)); @@ -121,7 +120,7 @@ }); test('render length uses number of bytes not characters', function () { - $template = Template::parse('{% if true %}すごい{% endif %}'); + $template = parseTemplate('{% if true %}すごい{% endif %}'); $context = new Context(resourceLimits: new ResourceLimits(renderLengthLimit: 8)); expect(fn () => $template->render($context))->toThrow(ResourceLimitException::class); $context = new Context(resourceLimits: new ResourceLimits(renderLengthLimit: 9)); @@ -129,7 +128,7 @@ }); test('undefined variables', function () { - $template = Template::parse('{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}'); + $template = parseTemplate('{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}'); $context = new Context( staticEnvironment: [ 'x' => 33, @@ -152,7 +151,7 @@ }); test('null value does not throw exception', function () { - $template = Template::parse('some{{x}}thing'); + $template = parseTemplate('some{{x}}thing'); $context = new Context( staticEnvironment: [ 'x' => null, @@ -168,7 +167,7 @@ }); test('undefined drop method', function () { - $template = Template::parse('{{ d.text }} {{ d.undefined }}'); + $template = parseTemplate('{{ d.text }} {{ d.undefined }}'); $context = new Context( staticEnvironment: [ 'd' => new \Keepsuit\Liquid\Tests\Stubs\TextDrop(), @@ -185,7 +184,7 @@ }); test('undefined drop method throw exception', function () { - $template = Template::parse('{{ d.text }} {{ d.undefined }}'); + $template = parseTemplate('{{ d.text }} {{ d.undefined }}'); $context = new Context( staticEnvironment: [ 'd' => new \Keepsuit\Liquid\Tests\Stubs\TextDrop(), @@ -198,7 +197,7 @@ }); test('undefined filter', function () { - $template = Template::parse('{{a}} {{x | upcase | somefilter1 | somefilter2 | downcase}}'); + $template = parseTemplate('{{a}} {{x | upcase | somefilter1 | somefilter2 | downcase}}'); $context = new Context( staticEnvironment: [ 'a' => 123, @@ -216,7 +215,7 @@ }); test('undefined filter throw exception', function () { - $template = Template::parse('{{a}} {{x | upcase | somefilter1 | somefilter2 | downcase}}'); + $template = parseTemplate('{{a}} {{x | upcase | somefilter1 | somefilter2 | downcase}}'); $context = new Context( staticEnvironment: [ 'a' => 123, diff --git a/tests/Pest.php b/tests/Pest.php index 35d58a3..54145bd 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -3,6 +3,7 @@ use Keepsuit\Liquid\Exceptions\SyntaxException; use Keepsuit\Liquid\Render\Context; use Keepsuit\Liquid\Template; +use Keepsuit\Liquid\TemplateFactory; use Keepsuit\Liquid\Tests\Stubs\StubFileSystem; use PHPUnit\Framework\ExpectationFailedException; @@ -11,6 +12,17 @@ function fixture(string $path): string return __DIR__.'/fixtures/'.$path; } +/** + * @throws SyntaxException + */ +function parseTemplate( + string $source, + bool $lineNumbers = true, + TemplateFactory $factory = null, +): Template { + return ($factory ?? TemplateFactory::new())->parse($source, lineNumbers: $lineNumbers); +} + /** * @throws SyntaxException */ @@ -20,8 +32,9 @@ function renderTemplate( array $registers = [], array $partials = [], bool $renderErrors = false, + TemplateFactory $factory = null, ): string { - $template = Template::parse($template, lineNumbers: true); + $template = parseTemplate($template, factory: $factory); $fileSystem = new StubFileSystem(partials: $partials); @@ -45,13 +58,15 @@ function assertTemplateResult( array $registers = [], array $partials = [], bool $renderErrors = false, + TemplateFactory $factory = null, ): void { expect(renderTemplate( template: $template, assigns: $assigns, registers: $registers, partials: $partials, - renderErrors: $renderErrors + renderErrors: $renderErrors, + factory: $factory, ))->toBe($expected); } diff --git a/tests/Unit/BlockTest.php b/tests/Unit/BlockTest.php index 168ebdd..3aced56 100644 --- a/tests/Unit/BlockTest.php +++ b/tests/Unit/BlockTest.php @@ -2,16 +2,15 @@ use Keepsuit\Liquid\Nodes\Variable; use Keepsuit\Liquid\Tags\CommentTag; -use Keepsuit\Liquid\Template; test('blankspace', function () { - $template = Template::parse(' '); + $template = parseTemplate(' '); expect($template->root->nodeList())->toBe([' ']); }); test('variable beginning', function () { - $template = Template::parse('{{funk}} '); + $template = parseTemplate('{{funk}} '); expect($template->root->nodeList()) ->toHaveCount(2) @@ -20,7 +19,7 @@ }); test('variable end', function () { - $template = Template::parse(' {{funk}}'); + $template = parseTemplate(' {{funk}}'); expect($template->root->nodeList()) ->toHaveCount(2) @@ -29,7 +28,7 @@ }); test('variable middle', function () { - $template = Template::parse(' {{funk}} '); + $template = parseTemplate(' {{funk}} '); expect($template->root->nodeList()) ->toHaveCount(3) @@ -39,7 +38,7 @@ }); test('variable many embedded fragments', function () { - $template = Template::parse(' {{funk}} {{so}} {{brother}} '); + $template = parseTemplate(' {{funk}} {{so}} {{brother}} '); expect($template->root->nodeList()) ->toHaveCount(7) @@ -53,7 +52,7 @@ }); test('with block', function () { - $template = Template::parse(' {% comment %} {% endcomment %} '); + $template = parseTemplate(' {% comment %} {% endcomment %} '); expect($template->root->nodeList()) ->toHaveCount(3) diff --git a/tests/Unit/ParseTreeVisitorTest.php b/tests/Unit/ParseTreeVisitorTest.php index d35c5aa..34322b3 100644 --- a/tests/Unit/ParseTreeVisitorTest.php +++ b/tests/Unit/ParseTreeVisitorTest.php @@ -3,7 +3,6 @@ use Keepsuit\Liquid\Nodes\VariableLookup; use Keepsuit\Liquid\Parse\ParseTreeVisitor; use Keepsuit\Liquid\Support\Arr; -use Keepsuit\Liquid\Template; test('variable', function () { expect(visit('{{ test }}'))->toBe(['test']); @@ -145,7 +144,7 @@ function traversal(string $source): ParseTreeVisitor { - return ParseTreeVisitor::for(Template::parse($source)->root) + return ParseTreeVisitor::for(parseTemplate($source)->root) ->addCallbackFor(VariableLookup::class, fn (VariableLookup $node) => [$node->name, null]); } diff --git a/tests/Unit/Tags/CaseTagTest.php b/tests/Unit/Tags/CaseTagTest.php index 98dfc9b..2b01957 100644 --- a/tests/Unit/Tags/CaseTagTest.php +++ b/tests/Unit/Tags/CaseTagTest.php @@ -1,10 +1,9 @@ root->nodeList()) ->toHaveCount(1) diff --git a/tests/Unit/Tags/ForTagTest.php b/tests/Unit/Tags/ForTagTest.php index b99cf71..938f785 100644 --- a/tests/Unit/Tags/ForTagTest.php +++ b/tests/Unit/Tags/ForTagTest.php @@ -1,10 +1,9 @@ root->nodeList()) ->toHaveCount(1) @@ -14,7 +13,7 @@ }); test('for else nodelist', function () { - $template = Template::parse('{% for item in items %}FOR{% else %}ELSE{% endfor %}'); + $template = parseTemplate('{% for item in items %}FOR{% else %}ELSE{% endfor %}'); expect($template->root->nodeList()) ->toHaveCount(1) diff --git a/tests/Unit/Tags/IfTagTest.php b/tests/Unit/Tags/IfTagTest.php index 1591b14..f859914 100644 --- a/tests/Unit/Tags/IfTagTest.php +++ b/tests/Unit/Tags/IfTagTest.php @@ -1,10 +1,9 @@ root->nodeList()) ->toHaveCount(1) diff --git a/tests/Unit/TemplateText.php b/tests/Unit/TemplateText.php index cb059d5..e0b7081 100644 --- a/tests/Unit/TemplateText.php +++ b/tests/Unit/TemplateText.php @@ -5,7 +5,7 @@ use Keepsuit\Liquid\Template; it('sets default localization in document', function () { - $template = Template::parse('{%comment%}{%endcomment%}'); + $template = parseTemplate('{%comment%}{%endcomment%}'); expect($template) ->root->nodeList()->toHaveCount(1) @@ -14,7 +14,7 @@ }); it('sets default localization in context with quick initialization', function () { - $template = Template::parse('{%comment%}{%endcomment%}', [ + $template = parseTemplate('{%comment%}{%endcomment%}', [ 'locale' => $i18n = new I18n(fixture('en_locale.yml')), ]); diff --git a/tests/Unit/TokenizerTest.php b/tests/Unit/TokenizerTest.php index e9f7c94..0cd796b 100644 --- a/tests/Unit/TokenizerTest.php +++ b/tests/Unit/TokenizerTest.php @@ -43,7 +43,7 @@ function tokenize(string $source): array function tokenizeLineNumbers(string $source): array { - $tokenizer = (new ParseContext())->newTokenizer($source, startLineNumber: 1); + $tokenizer = (new ParseContext(startLineNumber: 1))->newTokenizer($source); $lineNumbers = []; foreach ($tokenizer->shift() as $ignored) { From b2af5781ec8e56eb3784035983046d3060f66c47 Mon Sep 17 00:00:00 2001 From: Fabio Capucci Date: Thu, 31 Aug 2023 14:46:43 +0200 Subject: [PATCH 2/2] moved setup methods to template factory --- performance/CompiledThemeTestTemplate.php | 8 +- performance/Shopify/CustomFilters.php | 9 +- performance/ThemeTestTemplate.php | 1 + performance/benchmark.php | 4 +- src/Concerns/ContextAware.php | 15 + src/Drop.php | 11 +- src/Filters/FiltersProvider.php | 11 + src/Filters/StandardFilters.php | 8 +- src/Parse/ParseContext.php | 2 +- src/Render/Context.php | 14 +- src/Render/ContextSharedState.php | 2 - src/Render/FilterRegistry.php | 93 ------ src/Support/FilterRegistry.php | 63 ++++ src/Template.php | 2 +- src/TemplateFactory.php | 108 ++++++- tests/Integration/ContextTest.php | 12 +- tests/Integration/FilterTest.php | 33 +- tests/Integration/OutputTest.php | 85 ++--- tests/Integration/StandardFilterTest.php | 362 +++++++++++----------- tests/Integration/Tags/AssignTagTest.php | 11 +- tests/Pest.php | 16 +- 21 files changed, 475 insertions(+), 395 deletions(-) create mode 100644 src/Concerns/ContextAware.php create mode 100644 src/Filters/FiltersProvider.php delete mode 100644 src/Render/FilterRegistry.php create mode 100644 src/Support/FilterRegistry.php diff --git a/performance/CompiledThemeTestTemplate.php b/performance/CompiledThemeTestTemplate.php index 0585ba5..645016f 100644 --- a/performance/CompiledThemeTestTemplate.php +++ b/performance/CompiledThemeTestTemplate.php @@ -2,13 +2,14 @@ namespace Keepsuit\Liquid\Performance; -use Keepsuit\Liquid\Performance\Shopify\CustomFilters; use Keepsuit\Liquid\Render\Context; use Keepsuit\Liquid\Template; +use Keepsuit\Liquid\TemplateFactory; class CompiledThemeTestTemplate { public function __construct( + protected TemplateFactory $factory, public string $templateName, public Template $template, public ?Template $layout, @@ -34,11 +35,8 @@ public function render(array $assigns = []): void protected function buildContext(array $assigns = []): Context { - return new Context( + return $this->factory->newRenderContext( staticEnvironment: $assigns, - filters: [ - CustomFilters::class, - ], ); } } diff --git a/performance/Shopify/CustomFilters.php b/performance/Shopify/CustomFilters.php index 6c03cb5..c9866fc 100644 --- a/performance/Shopify/CustomFilters.php +++ b/performance/Shopify/CustomFilters.php @@ -3,15 +3,10 @@ namespace Keepsuit\Liquid\Performance\Shopify; use Keepsuit\Liquid\Exceptions\InvalidArgumentException; -use Keepsuit\Liquid\Render\Context; +use Keepsuit\Liquid\Filters\FiltersProvider; -class CustomFilters +class CustomFilters extends FiltersProvider { - public function __construct( - protected Context $context - ) { - } - public function json(mixed $value): string { if (is_array($value) && array_key_exists('collections', $value)) { diff --git a/performance/ThemeTestTemplate.php b/performance/ThemeTestTemplate.php index 7904d11..b07ec6a 100644 --- a/performance/ThemeTestTemplate.php +++ b/performance/ThemeTestTemplate.php @@ -25,6 +25,7 @@ public function compile(): CompiledThemeTestTemplate $layout = $this->layoutLiquid !== null ? $this->factory->parse($this->layoutLiquid) : null; return new CompiledThemeTestTemplate( + factory: $this->factory, templateName: $this->templateName, template: $template, layout: $layout, diff --git a/performance/benchmark.php b/performance/benchmark.php index e4fe350..bbf439a 100644 --- a/performance/benchmark.php +++ b/performance/benchmark.php @@ -4,6 +4,7 @@ use Keepsuit\Liquid\Performance\BenchmarkResult; use Keepsuit\Liquid\Performance\BenchmarkRunner; use Keepsuit\Liquid\Performance\Shopify\CommentFormTag; +use Keepsuit\Liquid\Performance\Shopify\CustomFilters; use Keepsuit\Liquid\Performance\Shopify\PaginateTag; use Keepsuit\Liquid\Performance\ThemeRunner; use Keepsuit\Liquid\TemplateFactory; @@ -17,7 +18,8 @@ $templateFactory = TemplateFactory::new() ->registerTag(CommentFormTag::class) - ->registerTag(PaginateTag::class); + ->registerTag(PaginateTag::class) + ->registerFilter(CustomFilters::class); (new SingleCommandApplication()) ->setName('Benchmark') diff --git a/src/Concerns/ContextAware.php b/src/Concerns/ContextAware.php new file mode 100644 index 0000000..f26217e --- /dev/null +++ b/src/Concerns/ContextAware.php @@ -0,0 +1,15 @@ +context = $context; + } +} diff --git a/src/Drop.php b/src/Drop.php index b9a8cf3..1654494 100644 --- a/src/Drop.php +++ b/src/Drop.php @@ -2,27 +2,22 @@ namespace Keepsuit\Liquid; +use Keepsuit\Liquid\Concerns\ContextAware; use Keepsuit\Liquid\Contracts\IsContextAware; use Keepsuit\Liquid\Contracts\MapsToLiquid; use Keepsuit\Liquid\Drops\DropMethodPrivate; use Keepsuit\Liquid\Exceptions\UndefinedDropMethodException; -use Keepsuit\Liquid\Render\Context; use Keepsuit\Liquid\Support\Str; class Drop implements IsContextAware, MapsToLiquid { - protected ?Context $context = null; + use ContextAware; /** * @var string[] */ private ?array $invokableMethods = null; - public function setContext(Context $context): void - { - $this->context = $context; - } - public function toLiquid(): mixed { return $this; @@ -35,7 +30,7 @@ public function toLiquidValue(): mixed protected function liquidMethodMissing(string $name): mixed { - if ($this->context?->strictVariables) { + if ($this->context->strictVariables) { throw new UndefinedDropMethodException($name); } diff --git a/src/Filters/FiltersProvider.php b/src/Filters/FiltersProvider.php new file mode 100644 index 0000000..d573653 --- /dev/null +++ b/src/Filters/FiltersProvider.php @@ -0,0 +1,11 @@ +lineNumber = match (true) { is_int($startLineNumber) => $startLineNumber, diff --git a/src/Render/Context.php b/src/Render/Context.php index 88f20a3..482a5b4 100644 --- a/src/Render/Context.php +++ b/src/Render/Context.php @@ -20,6 +20,7 @@ use Keepsuit\Liquid\Parse\ParseContext; use Keepsuit\Liquid\Profiler\Profiler; use Keepsuit\Liquid\Support\Arr; +use Keepsuit\Liquid\Support\FilterRegistry; use Keepsuit\Liquid\Support\MissingValue; use Keepsuit\Liquid\TagBlock; use Keepsuit\Liquid\Template; @@ -51,8 +52,6 @@ final class Context */ protected array $interrupts = []; - protected ?FilterRegistry $filterRegistry = null; - protected ?Profiler $profiler; public function __construct( @@ -63,11 +62,10 @@ public function __construct( /** array */ protected array $outerScope = [], array $registers = [], - /** @var array $filters */ - array $filters = [], protected bool $rethrowExceptions = false, public readonly bool $strictVariables = false, bool $profile = false, + protected FilterRegistry $filterRegistry = new FilterRegistry(), public readonly ResourceLimits $resourceLimits = new ResourceLimits(), public readonly LiquidFileSystem $fileSystem = new BlankFileSystem(), ) { @@ -76,7 +74,6 @@ public function __construct( $this->sharedState = new ContextSharedState( staticEnvironment: $staticEnvironment, staticRegisters: $registers, - filters: $filters ); $this->profiler = $profile ? new Profiler() : null; @@ -221,11 +218,7 @@ public function internalContextLookup(array|object $scope, int|string $key): mix public function applyFilter(string $filter, mixed $value, mixed ...$args): mixed { - if ($this->filterRegistry === null) { - $this->filterRegistry = FilterRegistry::createWithFilters($this, $this->sharedState->filters); - } - - return $this->filterRegistry->invoke($filter, $value, ...$args); + return $this->filterRegistry->invoke($this, $filter, $value, ...$args); } public function getRegister(string $name): mixed @@ -345,6 +338,7 @@ public function newIsolatedSubContext(?string $templateName): Context $this->checkOverflow(); $subContext = new Context( + filterRegistry: $this->filterRegistry, rethrowExceptions: $this->rethrowExceptions, resourceLimits: $this->resourceLimits, fileSystem: $this->fileSystem, diff --git a/src/Render/ContextSharedState.php b/src/Render/ContextSharedState.php index 21bc563..6462305 100644 --- a/src/Render/ContextSharedState.php +++ b/src/Render/ContextSharedState.php @@ -25,8 +25,6 @@ public function __construct( public array $staticRegisters = [], /** @var array<\Throwable> */ public array $errors = [], - /** @var array $filters */ - public array $filters = [], /** @var array */ public array $disabledTags = [], ) { diff --git a/src/Render/FilterRegistry.php b/src/Render/FilterRegistry.php deleted file mode 100644 index 91ccdb0..0000000 --- a/src/Render/FilterRegistry.php +++ /dev/null @@ -1,93 +0,0 @@ - - */ - protected array $filters = []; - - protected array $filterClasses = []; - - public function __construct( - protected Context $context - ) { - $this->addFilter(StandardFilters::class); - } - - public static function createWithFilters(Context $context, array $filters): FilterRegistry - { - $registry = new self($context); - - foreach ($filters as $filter) { - $registry->addFilter($filter); - } - - return $registry; - } - - /** - * @param class-string $filterClass - */ - public function addFilter(string $filterClass): static - { - if (! class_exists($filterClass)) { - throw new InvalidArgumentException("Filter class $filterClass does not exist."); - } - - $reflection = new \ReflectionClass($filterClass); - foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { - if (str_starts_with($method->getName(), '__')) { - continue; - } - - $this->filters[Str::snake($method->getName())] = function (...$args) use ($filterClass, $method) { - $filterClassInstance = $this->getFilterClassInstance($filterClass); - - return $filterClassInstance->{$method->getName()}(...$args); - }; - } - - return $this; - } - - /** - * @throws UndefinedFilterException - */ - public function invoke(string $filterName, mixed $value, mixed ...$args): mixed - { - $filter = $this->filters[$filterName] ?? null; - - if ($filter !== null) { - return $filter($value, ...$args); - } - - if ($this->context->strictVariables) { - throw new UndefinedFilterException($filterName); - } - - return $value; - } - - /** - * @template T of object - * - * @param class-string $filterClass - * @return T - */ - protected function getFilterClassInstance(string $filterClass) - { - if (! isset($this->filterClasses[$filterClass])) { - $this->filterClasses[$filterClass] = new $filterClass($this->context); - } - - return $this->filterClasses[$filterClass]; - } -} diff --git a/src/Support/FilterRegistry.php b/src/Support/FilterRegistry.php new file mode 100644 index 0000000..0cf1231 --- /dev/null +++ b/src/Support/FilterRegistry.php @@ -0,0 +1,63 @@ + + */ + protected array $filters = []; + + /** + * @param class-string $filterClass + */ + public function register(string $filterClass): static + { + if (! class_exists($filterClass)) { + throw new InvalidArgumentException("Filter class $filterClass does not exist."); + } + + $reflection = new \ReflectionClass($filterClass); + foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + if (str_starts_with($method->getName(), '__')) { + continue; + } + + $this->filters[Str::snake($method->getName())] = function (Context $context, ...$args) use ($filterClass, $method) { + $filterClassInstance = new $filterClass(); + + if ($filterClassInstance instanceof IsContextAware) { + $filterClassInstance->setContext($context); + } + + return $filterClassInstance->{$method->getName()}(...$args); + }; + } + + return $this; + } + + /** + * @throws UndefinedFilterException + */ + public function invoke(Context $context, string $filterName, mixed $value, mixed ...$args): mixed + { + $filter = $this->filters[$filterName] ?? null; + + if ($filter !== null) { + return $filter($context, $value, ...$args); + } + + if ($context->strictVariables) { + throw new UndefinedFilterException($filterName); + } + + return $value; + } +} diff --git a/src/Template.php b/src/Template.php index c8b6961..4737183 100644 --- a/src/Template.php +++ b/src/Template.php @@ -36,7 +36,7 @@ public static function parse(ParseContext $parseContext, string $source, string ); } - public function render(Context $context = new Context()): string + public function render(Context $context): string { $this->profiler = $context->getProfiler(); diff --git a/src/TemplateFactory.php b/src/TemplateFactory.php index 17a9f3d..4400ffe 100644 --- a/src/TemplateFactory.php +++ b/src/TemplateFactory.php @@ -2,17 +2,34 @@ namespace Keepsuit\Liquid; +use Keepsuit\Liquid\Contracts\LiquidFileSystem; use Keepsuit\Liquid\Exceptions\SyntaxException; +use Keepsuit\Liquid\FileSystems\BlankFileSystem; +use Keepsuit\Liquid\Filters\StandardFilters; use Keepsuit\Liquid\Parse\ParseContext; +use Keepsuit\Liquid\Render\Context; +use Keepsuit\Liquid\Render\ResourceLimits; +use Keepsuit\Liquid\Support\FilterRegistry; use Keepsuit\Liquid\Support\TagRegistry; final class TemplateFactory { public readonly TagRegistry $tagRegistry; + public readonly FilterRegistry $filterRegistry; + + protected bool $profile = false; + + protected LiquidFileSystem $fileSystem; + + protected ResourceLimits $resourceLimits; + public function __construct() { $this->tagRegistry = $this->buildTagRegistry(); + $this->filterRegistry = $this->buildFilterRegistry(); + $this->fileSystem = new BlankFileSystem(); + $this->resourceLimits = new ResourceLimits(); } public static function new(): TemplateFactory @@ -20,6 +37,62 @@ public static function new(): TemplateFactory return new self(); } + public function profile(bool $profile = true): TemplateFactory + { + $this->profile = $profile; + + return $this; + } + + public function setFilesystem(LiquidFileSystem $fileSystem): TemplateFactory + { + $this->fileSystem = $fileSystem; + + return $this; + } + + public function setResourceLimits(ResourceLimits $resourceLimits): TemplateFactory + { + $this->resourceLimits = $resourceLimits; + + return $this; + } + + public function newParseContext( + bool $lineNumbers = false, + ): ParseContext { + return new ParseContext( + startLineNumber: $lineNumbers || $this->profile, + tagRegistry: $this->tagRegistry + ); + } + + public function newRenderContext( + /** @var array $environment */ + array $environment = [], + /** @var array $staticEnvironment */ + array $staticEnvironment = [], + /** @var array $outerScope */ + array $outerScope = [], + /** @var array $registers */ + array $registers = [], + bool $rethrowExceptions = false, + bool $strictVariables = false, + ): Context { + return new Context( + environment: $environment, + staticEnvironment: $staticEnvironment, + outerScope: $outerScope, + registers: $registers, + rethrowExceptions: $rethrowExceptions, + strictVariables: $strictVariables, + profile: $this->profile, + filterRegistry: $this->filterRegistry, + resourceLimits: $this->resourceLimits, + fileSystem: $this->fileSystem, + ); + } + /** * @throws SyntaxException */ @@ -27,12 +100,27 @@ public function parse( string $source, bool $lineNumbers = false, ): Template { - $parseContext = new ParseContext( - startLineNumber: $lineNumbers, - tagRegistry: $this->tagRegistry - ); + return Template::parse($this->newParseContext($lineNumbers), $source); + } + + /** + * @param class-string $tag + */ + public function registerTag(string $tag): TemplateFactory + { + $this->tagRegistry->register($tag); + + return $this; + } - return Template::parse($parseContext, $source); + /** + * @param class-string $filtersProvider + */ + public function registerFilter(string $filtersProvider): TemplateFactory + { + $this->filterRegistry->register($filtersProvider); + + return $this; } protected function buildTagRegistry(): TagRegistry @@ -59,13 +147,9 @@ protected function buildTagRegistry(): TagRegistry ->register(Tags\UnlessTag::class); } - /** - * @param class-string $tag - */ - public function registerTag(string $tag): TemplateFactory + protected function buildFilterRegistry(): FilterRegistry { - $this->tagRegistry->register($tag); - - return $this; + return (new FilterRegistry()) + ->register(StandardFilters::class); } } diff --git a/tests/Integration/ContextTest.php b/tests/Integration/ContextTest.php index 0c8ff5c..b6ff520 100644 --- a/tests/Integration/ContextTest.php +++ b/tests/Integration/ContextTest.php @@ -59,10 +59,14 @@ }); test('add filter', function () { - $context = new Context(filters: [\Keepsuit\Liquid\Tests\Stubs\TestFilters::class]); + $context = \Keepsuit\Liquid\TemplateFactory::new() + ->registerFilter(\Keepsuit\Liquid\Tests\Stubs\TestFilters::class) + ->newRenderContext(); + expect($context->applyFilter('hi', 'hi?'))->toBe('hi? hi!'); - $context = new Context(); + $context = \Keepsuit\Liquid\TemplateFactory::new() + ->newRenderContext(); expect($context->applyFilter('hi', 'hi?'))->toBe('hi?'); }); @@ -434,7 +438,9 @@ function () use (&$global) { }); test('new isolated subcontext inherit filters', function () { - $context = new Context(filters: [\Keepsuit\Liquid\Tests\Stubs\TestFilters::class]); + $context = \Keepsuit\Liquid\TemplateFactory::new() + ->registerFilter(\Keepsuit\Liquid\Tests\Stubs\TestFilters::class) + ->newRenderContext(); $subContext = $context->newIsolatedSubContext('sub'); expect(parseTemplate('{{ "hi?" | hi }}')->render($subContext))->toBe('hi? hi!'); diff --git a/tests/Integration/FilterTest.php b/tests/Integration/FilterTest.php index 18ca865..1613431 100644 --- a/tests/Integration/FilterTest.php +++ b/tests/Integration/FilterTest.php @@ -1,6 +1,6 @@ registerFilter(MoneyFilters::class) + ->newRenderContext(); $context->set('var', 1000); expect(parseTemplate('{{ var | money }}')->render($context)) @@ -18,9 +18,9 @@ }); test('underscore in filter name', function () { - $context = new Context( - filters: [MoneyFilters::class] - ); + $context = TemplateFactory::new() + ->registerFilter(MoneyFilters::class) + ->newRenderContext(); $context->set('var', 1000); expect(parseTemplate('{{ var | money_with_underscore }}')->render($context)) @@ -28,9 +28,10 @@ }); test('second filter override first', function () { - $context = new Context( - filters: [MoneyFilters::class, CanadianMoneyFilter::class] - ); + $context = TemplateFactory::new() + ->registerFilter(MoneyFilters::class) + ->registerFilter(CanadianMoneyFilter::class) + ->newRenderContext(); $context->set('var', 1000); expect(parseTemplate('{{ var | money }}')->render($context)) @@ -119,9 +120,9 @@ }); test('filter with keyword arguments', function () { - $context = new Context( - filters: [SubstituteFilter::class] - ); + $context = TemplateFactory::new() + ->registerFilter(SubstituteFilter::class) + ->newRenderContext(); $context->set('surname', 'john'); $context->set('input', 'hello %{first_name}, %{last_name}'); @@ -130,9 +131,9 @@ }); test('can parse data keyword args', function () { - $context = new Context( - filters: [HtmlAttributesFilter::class] - ); + $context = TemplateFactory::new() + ->registerFilter(HtmlAttributesFilter::class) + ->newRenderContext(); expect(parseTemplate("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}")->render($context)) ->toBe("data-src='src' data-widths='100, 200'"); diff --git a/tests/Integration/OutputTest.php b/tests/Integration/OutputTest.php index 10ec09a..46da4b7 100644 --- a/tests/Integration/OutputTest.php +++ b/tests/Integration/OutputTest.php @@ -1,9 +1,11 @@ templateFactory = TemplateFactory::new(); + $this->assigns = [ 'car' => [ 'bmw' => 'good', @@ -32,90 +34,99 @@ }); test('variable piping', function () { - $context = new Context( - staticEnvironment: $this->assigns, - filters: [FunnyFilter::class] - ); + $context = $this->templateFactory + ->registerFilter(FunnyFilter::class) + ->newRenderContext( + staticEnvironment: $this->assigns, + ); expect(parseTemplate(' {{ car.gm | make_funny }} ')->render($context)) ->toBe(' LOL '); }); test('variable piping with input', function () { - $context = new Context( - staticEnvironment: $this->assigns, - filters: [FunnyFilter::class] - ); + $context = $this->templateFactory + ->registerFilter(FunnyFilter::class) + ->newRenderContext( + staticEnvironment: $this->assigns, + ); expect(parseTemplate(' {{ car.gm | cite_funny }} ')->render($context)) ->toBe(' LOL: bad '); }); test('variable piping with args', function () { - $context = new Context( - staticEnvironment: $this->assigns, - filters: [FunnyFilter::class] - ); + $context = $this->templateFactory + ->registerFilter(FunnyFilter::class) + ->newRenderContext( + staticEnvironment: $this->assigns, + ); expect(parseTemplate(" {{ car.gm | add_smiley : ':-(' }} ")->render($context)) ->toBe(' bad :-( '); }); test('variable piping with no args', function () { - $context = new Context( - staticEnvironment: $this->assigns, - filters: [FunnyFilter::class] - ); + $context = $this->templateFactory + ->registerFilter(FunnyFilter::class) + ->newRenderContext( + staticEnvironment: $this->assigns, + ); expect(parseTemplate(' {{ car.gm | add_smiley }} ')->render($context)) ->toBe(' bad :-) '); }); test('multiple variable piping with args', function () { - $context = new Context( - staticEnvironment: $this->assigns, - filters: [FunnyFilter::class] - ); + $context = $this->templateFactory + ->registerFilter(FunnyFilter::class) + ->newRenderContext( + staticEnvironment: $this->assigns, + ); expect(parseTemplate(" {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} ")->render($context)) ->toBe(' bad :-( :-( '); }); test('variable piping with multiple args', function () { - $context = new Context( - staticEnvironment: $this->assigns, - filters: [FunnyFilter::class] - ); + $context = $this->templateFactory + ->registerFilter(FunnyFilter::class) + ->newRenderContext( + staticEnvironment: $this->assigns, + ); expect(parseTemplate(" {{ car.gm | add_tag : 'span', 'bar'}} ")->render($context)) ->toBe(' bad '); }); test('variable piping with variable args', function () { - $context = new Context( - staticEnvironment: $this->assigns, - filters: [FunnyFilter::class] - ); + $context = $this->templateFactory + ->registerFilter(FunnyFilter::class) + ->newRenderContext( + staticEnvironment: $this->assigns, + ); expect(parseTemplate(" {{ car.gm | add_tag : 'span', car.bmw}} ")->render($context)) ->toBe(' bad '); }); test('multiple pipings', function () { - $context = new Context( - staticEnvironment: ['best_cars' => 'bmw'], - filters: [FunnyFilter::class] - ); + $context = $this->templateFactory + ->registerFilter(FunnyFilter::class) + ->newRenderContext( + staticEnvironment: ['best_cars' => 'bmw'] + ); expect(parseTemplate(' {{ best_cars | cite_funny | paragraph }} ')->render($context)) ->toBe('

LOL: bmw

'); }); test('link to', function () { - $context = new Context( - staticEnvironment: $this->assigns, - filters: [FunnyFilter::class] - ); + $context = $this->templateFactory + ->registerFilter(FunnyFilter::class) + ->newRenderContext( + staticEnvironment: $this->assigns, + ); expect(parseTemplate(" {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} ")->render($context)) ->toBe(' Typo '); diff --git a/tests/Integration/StandardFilterTest.php b/tests/Integration/StandardFilterTest.php index 6869498..9a2f21c 100644 --- a/tests/Integration/StandardFilterTest.php +++ b/tests/Integration/StandardFilterTest.php @@ -7,146 +7,148 @@ use Keepsuit\Liquid\Tests\Stubs\ThingWithParamToLiquid; beforeEach(function () { - $this->filters = new \Keepsuit\Liquid\Render\FilterRegistry(new \Keepsuit\Liquid\Render\Context()); + $templateFactory = \Keepsuit\Liquid\TemplateFactory::new(); + $this->filters = $templateFactory->filterRegistry; + $this->context = $templateFactory->newRenderContext(); }); test('size', function () { - expect($this->filters->invoke('size', [1, 2, 3]))->toBe(3); - expect($this->filters->invoke('size', []))->toBe(0); - expect($this->filters->invoke('size', null))->toBe(0); + expect($this->filters->invoke($this->context, 'size', [1, 2, 3]))->toBe(3); + expect($this->filters->invoke($this->context, 'size', []))->toBe(0); + expect($this->filters->invoke($this->context, 'size', null))->toBe(0); }); test('downcase', function () { - expect($this->filters->invoke('downcase', 'Testing'))->toBe('testing'); - expect($this->filters->invoke('downcase', null))->toBe(''); + expect($this->filters->invoke($this->context, 'downcase', 'Testing'))->toBe('testing'); + expect($this->filters->invoke($this->context, 'downcase', null))->toBe(''); }); test('upcase', function () { - expect($this->filters->invoke('upcase', 'Testing'))->toBe('TESTING'); - expect($this->filters->invoke('upcase', null))->toBe(''); + expect($this->filters->invoke($this->context, 'upcase', 'Testing'))->toBe('TESTING'); + expect($this->filters->invoke($this->context, 'upcase', null))->toBe(''); }); test('slice', function () { - expect($this->filters->invoke('slice', 'foobar', 1, 3))->toBe('oob'); - expect($this->filters->invoke('slice', 'foobar', 1, 1000))->toBe('oobar'); - expect($this->filters->invoke('slice', 'foobar', 1, 0))->toBe(''); - expect($this->filters->invoke('slice', 'foobar', 1, 1))->toBe('o'); - expect($this->filters->invoke('slice', 'foobar', 3, 3))->toBe('bar'); - expect($this->filters->invoke('slice', 'foobar', -2, 2))->toBe('ar'); - expect($this->filters->invoke('slice', 'foobar', -2, 1000))->toBe('ar'); - expect($this->filters->invoke('slice', 'foobar', -1))->toBe('r'); - expect($this->filters->invoke('slice', null, 0))->toBe(''); - expect($this->filters->invoke('slice', 'foobar', 100, 10))->toBe(''); - expect($this->filters->invoke('slice', 'foobar', -100, 10))->toBe(''); - expect($this->filters->invoke('slice', 'foobar', '1', '3'))->toBe('oob'); + expect($this->filters->invoke($this->context, 'slice', 'foobar', 1, 3))->toBe('oob'); + expect($this->filters->invoke($this->context, 'slice', 'foobar', 1, 1000))->toBe('oobar'); + expect($this->filters->invoke($this->context, 'slice', 'foobar', 1, 0))->toBe(''); + expect($this->filters->invoke($this->context, 'slice', 'foobar', 1, 1))->toBe('o'); + expect($this->filters->invoke($this->context, 'slice', 'foobar', 3, 3))->toBe('bar'); + expect($this->filters->invoke($this->context, 'slice', 'foobar', -2, 2))->toBe('ar'); + expect($this->filters->invoke($this->context, 'slice', 'foobar', -2, 1000))->toBe('ar'); + expect($this->filters->invoke($this->context, 'slice', 'foobar', -1))->toBe('r'); + expect($this->filters->invoke($this->context, 'slice', null, 0))->toBe(''); + expect($this->filters->invoke($this->context, 'slice', 'foobar', 100, 10))->toBe(''); + expect($this->filters->invoke($this->context, 'slice', 'foobar', -100, 10))->toBe(''); + expect($this->filters->invoke($this->context, 'slice', 'foobar', '1', '3'))->toBe('oob'); }); test('slice on arrays', function () { $input = mb_str_split('foobar'); - expect($this->filters->invoke('slice', $input, 1, 3))->toBe(['o', 'o', 'b']); - expect($this->filters->invoke('slice', $input, 1, 1000))->toBe(['o', 'o', 'b', 'a', 'r']); - expect($this->filters->invoke('slice', $input, 1, 0))->toBe([]); - expect($this->filters->invoke('slice', $input, 1, 1))->toBe(['o']); - expect($this->filters->invoke('slice', $input, 3, 3))->toBe(['b', 'a', 'r']); - expect($this->filters->invoke('slice', $input, -2, 2))->toBe(['a', 'r']); - expect($this->filters->invoke('slice', $input, -2, 1000))->toBe(['a', 'r']); - expect($this->filters->invoke('slice', $input, -1))->toBe(['r']); - expect($this->filters->invoke('slice', $input, 100, 10))->toBe([]); - expect($this->filters->invoke('slice', $input, -100, 10))->toBe([]); + expect($this->filters->invoke($this->context, 'slice', $input, 1, 3))->toBe(['o', 'o', 'b']); + expect($this->filters->invoke($this->context, 'slice', $input, 1, 1000))->toBe(['o', 'o', 'b', 'a', 'r']); + expect($this->filters->invoke($this->context, 'slice', $input, 1, 0))->toBe([]); + expect($this->filters->invoke($this->context, 'slice', $input, 1, 1))->toBe(['o']); + expect($this->filters->invoke($this->context, 'slice', $input, 3, 3))->toBe(['b', 'a', 'r']); + expect($this->filters->invoke($this->context, 'slice', $input, -2, 2))->toBe(['a', 'r']); + expect($this->filters->invoke($this->context, 'slice', $input, -2, 1000))->toBe(['a', 'r']); + expect($this->filters->invoke($this->context, 'slice', $input, -1))->toBe(['r']); + expect($this->filters->invoke($this->context, 'slice', $input, 100, 10))->toBe([]); + expect($this->filters->invoke($this->context, 'slice', $input, -100, 10))->toBe([]); }); test('truncate', function () { - expect($this->filters->invoke('truncate', '1234567890', 7))->toBe('1234...'); - expect($this->filters->invoke('truncate', '1234567890', 20))->toBe('1234567890'); - expect($this->filters->invoke('truncate', '1234567890', 0))->toBe('...'); - expect($this->filters->invoke('truncate', '1234567890'))->toBe('1234567890'); - expect($this->filters->invoke('truncate', '测试测试测试测试', 5))->toBe('测试...'); - expect($this->filters->invoke('truncate', '1234567890', 5, 1))->toBe('12341'); + expect($this->filters->invoke($this->context, 'truncate', '1234567890', 7))->toBe('1234...'); + expect($this->filters->invoke($this->context, 'truncate', '1234567890', 20))->toBe('1234567890'); + expect($this->filters->invoke($this->context, 'truncate', '1234567890', 0))->toBe('...'); + expect($this->filters->invoke($this->context, 'truncate', '1234567890'))->toBe('1234567890'); + expect($this->filters->invoke($this->context, 'truncate', '测试测试测试测试', 5))->toBe('测试...'); + expect($this->filters->invoke($this->context, 'truncate', '1234567890', 5, 1))->toBe('12341'); }); test('split', function () { - expect($this->filters->invoke('split', '12~34', '~'))->toBe(['12', '34']); - expect($this->filters->invoke('split', 'A? ~ ~ ~ ,Z', '~ ~ ~'))->toBe(['A? ', ' ,Z']); - expect($this->filters->invoke('split', 'A?Z', '~'))->toBe(['A?Z']); - expect($this->filters->invoke('split', null, ' '))->toBe([]); - expect($this->filters->invoke('split', 'A1Z', 1))->toBe(['A', 'Z']); + expect($this->filters->invoke($this->context, 'split', '12~34', '~'))->toBe(['12', '34']); + expect($this->filters->invoke($this->context, 'split', 'A? ~ ~ ~ ,Z', '~ ~ ~'))->toBe(['A? ', ' ,Z']); + expect($this->filters->invoke($this->context, 'split', 'A?Z', '~'))->toBe(['A?Z']); + expect($this->filters->invoke($this->context, 'split', null, ' '))->toBe([]); + expect($this->filters->invoke($this->context, 'split', 'A1Z', 1))->toBe(['A', 'Z']); }); test('escape', function () { - expect($this->filters->invoke('escape', ''))->toBe('<strong>'); - expect($this->filters->invoke('escape', 1))->toBe('1'); - //expect($this->filters->invoke('escape',(new DateTime())->setDate(2001, 02, 03)))->toBe('2001-02-03'); - expect($this->filters->invoke('escape', null))->toBeNull; + expect($this->filters->invoke($this->context, 'escape', ''))->toBe('<strong>'); + expect($this->filters->invoke($this->context, 'escape', 1))->toBe('1'); + //expect($this->filters->invoke($this->context,'escape',(new DateTime())->setDate(2001, 02, 03)))->toBe('2001-02-03'); + expect($this->filters->invoke($this->context, 'escape', null))->toBeNull; }); test('escape once', function () { - expect($this->filters->invoke('escape_once', '<strong>Hulk'))->toBe('<strong>Hulk</strong>'); + expect($this->filters->invoke($this->context, 'escape_once', '<strong>Hulk'))->toBe('<strong>Hulk</strong>'); }); test('base64 encode', function () { - expect($this->filters->invoke('base64_encode', 'one two three'))->toBe('b25lIHR3byB0aHJlZQ=='); - expect($this->filters->invoke('base64_encode', null))->toBe(''); + expect($this->filters->invoke($this->context, 'base64_encode', 'one two three'))->toBe('b25lIHR3byB0aHJlZQ=='); + expect($this->filters->invoke($this->context, 'base64_encode', null))->toBe(''); }); test('base64 decode', function () { - expect($this->filters->invoke('base64_decode', 'b25lIHR3byB0aHJlZQ=='))->toBe('one two three'); - expect($this->filters->invoke('base64_decode', null))->toBe(''); - expect(fn () => $this->filters->invoke('base64_decode', 'invalidbase64'))->toThrow(\Keepsuit\Liquid\Exceptions\InvalidArgumentException::class); + expect($this->filters->invoke($this->context, 'base64_decode', 'b25lIHR3byB0aHJlZQ=='))->toBe('one two three'); + expect($this->filters->invoke($this->context, 'base64_decode', null))->toBe(''); + expect(fn () => $this->filters->invoke($this->context, 'base64_decode', 'invalidbase64'))->toThrow(\Keepsuit\Liquid\Exceptions\InvalidArgumentException::class); }); test('url encode', function () { - expect($this->filters->invoke('url_encode', 'foo+1@example.com'))->toBe('foo%2B1%40example.com'); - expect($this->filters->invoke('url_encode', 1))->toBe('1'); - expect($this->filters->invoke('url_encode', null))->toBe(''); + expect($this->filters->invoke($this->context, 'url_encode', 'foo+1@example.com'))->toBe('foo%2B1%40example.com'); + expect($this->filters->invoke($this->context, 'url_encode', 1))->toBe('1'); + expect($this->filters->invoke($this->context, 'url_encode', null))->toBe(''); }); test('url decode', function () { - expect($this->filters->invoke('url_decode', 'foo+bar'))->toBe('foo bar'); - expect($this->filters->invoke('url_decode', 'foo%20bar'))->toBe('foo bar'); - expect($this->filters->invoke('url_decode', 'foo%2B1%40example.com'))->toBe('foo+1@example.com'); - expect($this->filters->invoke('url_decode', 1))->toBe('1'); - expect($this->filters->invoke('url_decode', null))->toBe(''); + expect($this->filters->invoke($this->context, 'url_decode', 'foo+bar'))->toBe('foo bar'); + expect($this->filters->invoke($this->context, 'url_decode', 'foo%20bar'))->toBe('foo bar'); + expect($this->filters->invoke($this->context, 'url_decode', 'foo%2B1%40example.com'))->toBe('foo+1@example.com'); + expect($this->filters->invoke($this->context, 'url_decode', 1))->toBe('1'); + expect($this->filters->invoke($this->context, 'url_decode', null))->toBe(''); }); test('truncatewords', function () { - expect($this->filters->invoke('truncatewords', 'one two three', 4))->toBe('one two three'); - expect($this->filters->invoke('truncatewords', 'one two three', 2))->toBe('one two...'); - expect($this->filters->invoke('truncatewords', 'one two three'))->toBe('one two three'); - expect($this->filters->invoke('truncatewords', 'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)) + expect($this->filters->invoke($this->context, 'truncatewords', 'one two three', 4))->toBe('one two three'); + expect($this->filters->invoke($this->context, 'truncatewords', 'one two three', 2))->toBe('one two...'); + expect($this->filters->invoke($this->context, 'truncatewords', 'one two three'))->toBe('one two three'); + expect($this->filters->invoke($this->context, 'truncatewords', 'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)) ->toBe('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...'); - expect($this->filters->invoke('truncatewords', '测试测试测试测试', 5))->toBe('测试测试测试测试'); - expect($this->filters->invoke('truncatewords', 'one two three', 2, 1))->toBe('one two1'); - expect($this->filters->invoke('truncatewords', "one two\tthree\nfour", 3))->toBe('one two three...'); - expect($this->filters->invoke('truncatewords', 'one two three four', 2))->toBe('one two...'); - expect($this->filters->invoke('truncatewords', 'one two three four', 0))->toBe('one...'); + expect($this->filters->invoke($this->context, 'truncatewords', '测试测试测试测试', 5))->toBe('测试测试测试测试'); + expect($this->filters->invoke($this->context, 'truncatewords', 'one two three', 2, 1))->toBe('one two1'); + expect($this->filters->invoke($this->context, 'truncatewords', "one two\tthree\nfour", 3))->toBe('one two three...'); + expect($this->filters->invoke($this->context, 'truncatewords', 'one two three four', 2))->toBe('one two...'); + expect($this->filters->invoke($this->context, 'truncatewords', 'one two three four', 0))->toBe('one...'); }); test('strip html', function () { - expect($this->filters->invoke('strip_html', '
test
'))->toBe('test'); - expect($this->filters->invoke('strip_html', "
test
"))->toBe('test'); - expect($this->filters->invoke('strip_html', ""))->toBe(''); - expect($this->filters->invoke('strip_html', ""))->toBe(''); - expect($this->filters->invoke('strip_html', "test"))->toBe('test'); - expect($this->filters->invoke('strip_html', "test"))->toBe('test'); - expect($this->filters->invoke('strip_html', null))->toBe(''); - expect($this->filters->invoke('strip_html', '<<'))->toBe('foo;'); + expect($this->filters->invoke($this->context, 'strip_html', '
test
'))->toBe('test'); + expect($this->filters->invoke($this->context, 'strip_html', "
test
"))->toBe('test'); + expect($this->filters->invoke($this->context, 'strip_html', ""))->toBe(''); + expect($this->filters->invoke($this->context, 'strip_html', ""))->toBe(''); + expect($this->filters->invoke($this->context, 'strip_html', "test"))->toBe('test'); + expect($this->filters->invoke($this->context, 'strip_html', "test"))->toBe('test'); + expect($this->filters->invoke($this->context, 'strip_html', null))->toBe(''); + expect($this->filters->invoke($this->context, 'strip_html', '<<'))->toBe('foo;'); }); test('join', function () { - expect($this->filters->invoke('join', [1, 2, 3, 4]))->toBe('1 2 3 4'); - expect($this->filters->invoke('join', [1, 2, 3, 4], ' - '))->toBe('1 - 2 - 3 - 4'); - expect($this->filters->invoke('join', [1, 2, 3, 4], 1))->toBe('1121314'); + expect($this->filters->invoke($this->context, 'join', [1, 2, 3, 4]))->toBe('1 2 3 4'); + expect($this->filters->invoke($this->context, 'join', [1, 2, 3, 4], ' - '))->toBe('1 - 2 - 3 - 4'); + expect($this->filters->invoke($this->context, 'join', [1, 2, 3, 4], 1))->toBe('1121314'); }); test('sort', function () { - expect($this->filters->invoke('sort', [4, 3, 2, 1]))->toBe([1, 2, 3, 4]); - expect($this->filters->invoke('sort', [['a' => 4], ['a' => 3], ['a' => 1], ['a' => 2]], 'a'))->toBe([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]]); - expect($this->filters->invoke('sort', [null, 4, 3, 2, 1]))->toBe([1, 2, 3, 4, null]); - expect($this->filters->invoke('sort', [['a' => 4], ['a' => 3], [], ['a' => 1], ['a' => 2]], 'a'))->toBe([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4], []]); + expect($this->filters->invoke($this->context, 'sort', [4, 3, 2, 1]))->toBe([1, 2, 3, 4]); + expect($this->filters->invoke($this->context, 'sort', [['a' => 4], ['a' => 3], ['a' => 1], ['a' => 2]], 'a'))->toBe([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]]); + expect($this->filters->invoke($this->context, 'sort', [null, 4, 3, 2, 1]))->toBe([1, 2, 3, 4, null]); + expect($this->filters->invoke($this->context, 'sort', [['a' => 4], ['a' => 3], [], ['a' => 1], ['a' => 2]], 'a'))->toBe([['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4], []]); }); test('sort when property is sometimes missing puts nulls last', function () { - expect($this->filters->invoke('sort', [ + expect($this->filters->invoke($this->context, 'sort', [ ['price' => 4, 'handle' => 'alpha'], ['handle' => 'beta'], ['price' => 1, 'handle' => 'gamma'], @@ -162,14 +164,14 @@ }); test('sort natural', function () { - expect($this->filters->invoke('sort_natural', ['c', 'D', 'a', 'B']))->toBe(['a', 'B', 'c', 'D']); - expect($this->filters->invoke('sort_natural', [['a' => 'D'], ['a' => 'c'], ['a' => 'a'], ['a' => 'B']], 'a'))->toBe([['a' => 'a'], ['a' => 'B'], ['a' => 'c'], ['a' => 'D']]); - expect($this->filters->invoke('sort_natural', [null, 'c', 'D', 'a', 'B']))->toBe(['a', 'B', 'c', 'D', null]); - expect($this->filters->invoke('sort_natural', [['a' => 'D'], ['a' => 'c'], [], ['a' => 'a'], ['a' => 'B']], 'a'))->toBe([['a' => 'a'], ['a' => 'B'], ['a' => 'c'], ['a' => 'D'], []]); + expect($this->filters->invoke($this->context, 'sort_natural', ['c', 'D', 'a', 'B']))->toBe(['a', 'B', 'c', 'D']); + expect($this->filters->invoke($this->context, 'sort_natural', [['a' => 'D'], ['a' => 'c'], ['a' => 'a'], ['a' => 'B']], 'a'))->toBe([['a' => 'a'], ['a' => 'B'], ['a' => 'c'], ['a' => 'D']]); + expect($this->filters->invoke($this->context, 'sort_natural', [null, 'c', 'D', 'a', 'B']))->toBe(['a', 'B', 'c', 'D', null]); + expect($this->filters->invoke($this->context, 'sort_natural', [['a' => 'D'], ['a' => 'c'], [], ['a' => 'a'], ['a' => 'B']], 'a'))->toBe([['a' => 'a'], ['a' => 'B'], ['a' => 'c'], ['a' => 'D'], []]); }); test('sort natural when property is sometimes missing puts nulls last', function () { - expect($this->filters->invoke('sort', [ + expect($this->filters->invoke($this->context, 'sort', [ ['price' => '4', 'handle' => 'alpha'], ['handle' => 'beta'], ['price' => '1', 'handle' => 'gamma'], @@ -185,7 +187,7 @@ }); test('sort natural case check', function () { - expect($this->filters->invoke('sort_natural', [ + expect($this->filters->invoke($this->context, 'sort_natural', [ ['key' => 'X'], ['key' => 'Y'], ['key' => 'Z'], @@ -202,44 +204,44 @@ ['key' => 'Z'], ['fake' => 't'], ]); - expect($this->filters->invoke('sort_natural', ['X', 'Y', 'Z', 'a', 'b', 'c']))->toBe(['a', 'b', 'c', 'X', 'Y', 'Z']); + expect($this->filters->invoke($this->context, 'sort_natural', ['X', 'Y', 'Z', 'a', 'b', 'c']))->toBe(['a', 'b', 'c', 'X', 'Y', 'Z']); }); test('sort empty array', function () { - expect($this->filters->invoke('sort', [], 'a'))->toBe([]); - expect($this->filters->invoke('sort_natural', [], 'a'))->toBe([]); + expect($this->filters->invoke($this->context, 'sort', [], 'a'))->toBe([]); + expect($this->filters->invoke($this->context, 'sort_natural', [], 'a'))->toBe([]); }); test('numerical vs lexicographical sort', function () { - expect($this->filters->invoke('sort', [10, 2]))->toBe([2, 10]); - expect($this->filters->invoke('sort', [['a' => 10], ['a' => 2]], 'a'))->toBe([['a' => 2], ['a' => 10]]); - expect($this->filters->invoke('sort', ['10', '2']))->toBe(['10', '2']); - expect($this->filters->invoke('sort', [['a' => '10'], ['a' => '2']], 'a'))->toBe([['a' => '10'], ['a' => '2']]); + expect($this->filters->invoke($this->context, 'sort', [10, 2]))->toBe([2, 10]); + expect($this->filters->invoke($this->context, 'sort', [['a' => 10], ['a' => 2]], 'a'))->toBe([['a' => 2], ['a' => 10]]); + expect($this->filters->invoke($this->context, 'sort', ['10', '2']))->toBe(['10', '2']); + expect($this->filters->invoke($this->context, 'sort', [['a' => '10'], ['a' => '2']], 'a'))->toBe([['a' => '10'], ['a' => '2']]); }); test('uniq', function () { - expect($this->filters->invoke('uniq', ['foo']))->toBe(['foo']); - expect($this->filters->invoke('uniq', [1, 1, 3, 2, 3, 1, 4, 3, 2, 1]))->toBe([1, 3, 2, 4]); - expect($this->filters->invoke('uniq', [['a' => 1], ['a' => 3], ['a' => 1], ['a' => 2]], 'a'))->toBe([['a' => 1], ['a' => 3], ['a' => 2]]); - expect($this->filters->invoke('uniq', [], 'a'))->toBe([]); + expect($this->filters->invoke($this->context, 'uniq', ['foo']))->toBe(['foo']); + expect($this->filters->invoke($this->context, 'uniq', [1, 1, 3, 2, 3, 1, 4, 3, 2, 1]))->toBe([1, 3, 2, 4]); + expect($this->filters->invoke($this->context, 'uniq', [['a' => 1], ['a' => 3], ['a' => 1], ['a' => 2]], 'a'))->toBe([['a' => 1], ['a' => 3], ['a' => 2]]); + expect($this->filters->invoke($this->context, 'uniq', [], 'a'))->toBe([]); $testDrop = new TestDrop('test'); $testDropAlternate = new TestDrop('test'); - expect($this->filters->invoke('uniq', [$testDrop, $testDropAlternate], 'value'))->toBe([$testDrop]); + expect($this->filters->invoke($this->context, 'uniq', [$testDrop, $testDropAlternate], 'value'))->toBe([$testDrop]); }); test('compact', function () { - expect($this->filters->invoke('compact', []))->toBe([]); - expect($this->filters->invoke('compact', [1, null, 2, 3]))->toBe([1, 2, 3]); - expect($this->filters->invoke('compact', [['a' => 1], ['a' => 3], [], ['a' => 2]], 'a'))->toBe([['a' => 1], ['a' => 3], ['a' => 2]]); + expect($this->filters->invoke($this->context, 'compact', []))->toBe([]); + expect($this->filters->invoke($this->context, 'compact', [1, null, 2, 3]))->toBe([1, 2, 3]); + expect($this->filters->invoke($this->context, 'compact', [['a' => 1], ['a' => 3], [], ['a' => 2]], 'a'))->toBe([['a' => 1], ['a' => 3], ['a' => 2]]); }); test('reverse', function () { - expect($this->filters->invoke('reverse', [1, 2, 3, 4]))->toBe([4, 3, 2, 1]); + expect($this->filters->invoke($this->context, 'reverse', [1, 2, 3, 4]))->toBe([4, 3, 2, 1]); }); test('map', function () { - expect($this->filters->invoke('map', [['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]], 'a'))->toBe([1, 2, 3, 4]); + expect($this->filters->invoke($this->context, 'map', [['a' => 1], ['a' => 2], ['a' => 3], ['a' => 4]], 'a'))->toBe([1, 2, 3, 4]); assertTemplateResult( 'abc', @@ -340,7 +342,7 @@ [3], ]; - expect(fn () => $this->filters->invoke('map', $foo, 'bar'))->toThrow(InvalidArgumentException::class); + expect(fn () => $this->filters->invoke($this->context, 'map', $foo, 'bar'))->toThrow(InvalidArgumentException::class); }); test('map returns empty with no property', function () { @@ -350,7 +352,7 @@ [3], ]; - expect(fn () => $this->filters->invoke('map', $foo, null))->toThrow(TypeError::class); + expect(fn () => $this->filters->invoke($this->context, 'map', $foo, null))->toThrow(TypeError::class); }); test('sort works on iterator', function () { @@ -383,58 +385,58 @@ }); test('date', function () { - expect($this->filters->invoke('date', new DateTime('2006-05-05 10:00:00'), '%B'))->toBe('May'); - expect($this->filters->invoke('date', new DateTime('2006-06-05 10:00:00'), '%B'))->toBe('June'); - expect($this->filters->invoke('date', new DateTime('2006-07-05 10:00:00'), '%B'))->toBe('July'); + expect($this->filters->invoke($this->context, 'date', new DateTime('2006-05-05 10:00:00'), '%B'))->toBe('May'); + expect($this->filters->invoke($this->context, 'date', new DateTime('2006-06-05 10:00:00'), '%B'))->toBe('June'); + expect($this->filters->invoke($this->context, 'date', new DateTime('2006-07-05 10:00:00'), '%B'))->toBe('July'); - expect($this->filters->invoke('date', '2006-05-05 10:00:00', '%B'))->toBe('May'); - expect($this->filters->invoke('date', '2006-06-05 10:00:00', '%B'))->toBe('June'); - expect($this->filters->invoke('date', '2006-07-05 10:00:00', '%B'))->toBe('July'); + expect($this->filters->invoke($this->context, 'date', '2006-05-05 10:00:00', '%B'))->toBe('May'); + expect($this->filters->invoke($this->context, 'date', '2006-06-05 10:00:00', '%B'))->toBe('June'); + expect($this->filters->invoke($this->context, 'date', '2006-07-05 10:00:00', '%B'))->toBe('July'); - expect($this->filters->invoke('date', '2006-07-05 10:00:00', ''))->toBe('2006-07-05 10:00:00'); - expect($this->filters->invoke('date', '2006-07-05 10:00:00', null))->toBe('2006-07-05 10:00:00'); + expect($this->filters->invoke($this->context, 'date', '2006-07-05 10:00:00', ''))->toBe('2006-07-05 10:00:00'); + expect($this->filters->invoke($this->context, 'date', '2006-07-05 10:00:00', null))->toBe('2006-07-05 10:00:00'); - expect($this->filters->invoke('date', '2006-07-05 10:00:00', '%m/%d/%Y'))->toBe('07/05/2006'); + expect($this->filters->invoke($this->context, 'date', '2006-07-05 10:00:00', '%m/%d/%Y'))->toBe('07/05/2006'); - expect($this->filters->invoke('date', 'Fri Jul 16 01:00:00 2004', '%m/%d/%Y'))->toBe('07/16/2004'); - expect($this->filters->invoke('date', 'now', '%Y'))->toBe(date('Y')); - expect($this->filters->invoke('date', 'today', '%Y'))->toBe(date('Y')); + expect($this->filters->invoke($this->context, 'date', 'Fri Jul 16 01:00:00 2004', '%m/%d/%Y'))->toBe('07/16/2004'); + expect($this->filters->invoke($this->context, 'date', 'now', '%Y'))->toBe(date('Y')); + expect($this->filters->invoke($this->context, 'date', 'today', '%Y'))->toBe(date('Y')); - expect($this->filters->invoke('date', null, '%B'))->toBeNull(); - expect($this->filters->invoke('date', '', '%B'))->toBe(''); + expect($this->filters->invoke($this->context, 'date', null, '%B'))->toBeNull(); + expect($this->filters->invoke($this->context, 'date', '', '%B'))->toBe(''); - expect($this->filters->invoke('date', 1152098955, '%m/%d/%Y'))->toBe('07/05/2006'); - expect($this->filters->invoke('date', '1152098955', '%m/%d/%Y'))->toBe('07/05/2006'); + expect($this->filters->invoke($this->context, 'date', 1152098955, '%m/%d/%Y'))->toBe('07/05/2006'); + expect($this->filters->invoke($this->context, 'date', '1152098955', '%m/%d/%Y'))->toBe('07/05/2006'); }); test('first last', function () { - expect($this->filters->invoke('first', [1, 2, 3]))->toBe(1); - expect($this->filters->invoke('last', [1, 2, 3]))->toBe(3); + expect($this->filters->invoke($this->context, 'first', [1, 2, 3]))->toBe(1); + expect($this->filters->invoke($this->context, 'last', [1, 2, 3]))->toBe(3); - expect($this->filters->invoke('first', []))->toBeNull(); - expect($this->filters->invoke('last', []))->toBeNull(); + expect($this->filters->invoke($this->context, 'first', []))->toBeNull(); + expect($this->filters->invoke($this->context, 'last', []))->toBeNull(); }); test('replace', function () { - expect($this->filters->invoke('replace', 'a a a a', 'a', 'b'))->toBe('b b b b'); - expect($this->filters->invoke('replace', '1 1 1 1', 1, 2))->toBe('2 2 2 2'); - expect($this->filters->invoke('replace', '1 1 1 1', 2, 3))->toBe('1 1 1 1'); + expect($this->filters->invoke($this->context, 'replace', 'a a a a', 'a', 'b'))->toBe('b b b b'); + expect($this->filters->invoke($this->context, 'replace', '1 1 1 1', 1, 2))->toBe('2 2 2 2'); + expect($this->filters->invoke($this->context, 'replace', '1 1 1 1', 2, 3))->toBe('1 1 1 1'); assertTemplateResult( '2 2 2 2', "{{ '1 1 1 1' | replace: '1', 2 }}", ); - expect($this->filters->invoke('replace_first', 'a a a a', 'a', 'b'))->toBe('b a a a'); - expect($this->filters->invoke('replace_first', '1 1 1 1', 1, 2))->toBe('2 1 1 1'); - expect($this->filters->invoke('replace_first', '1 1 1 1', 2, 3))->toBe('1 1 1 1'); + expect($this->filters->invoke($this->context, 'replace_first', 'a a a a', 'a', 'b'))->toBe('b a a a'); + expect($this->filters->invoke($this->context, 'replace_first', '1 1 1 1', 1, 2))->toBe('2 1 1 1'); + expect($this->filters->invoke($this->context, 'replace_first', '1 1 1 1', 2, 3))->toBe('1 1 1 1'); assertTemplateResult( '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}", ); - expect($this->filters->invoke('replace_last', 'a a a a', 'a', 'b'))->toBe('a a a b'); - expect($this->filters->invoke('replace_last', '1 1 1 1', 1, 2))->toBe('1 1 1 2'); - expect($this->filters->invoke('replace_last', '1 1 1 1', 2, 3))->toBe('1 1 1 1'); + expect($this->filters->invoke($this->context, 'replace_last', 'a a a a', 'a', 'b'))->toBe('a a a b'); + expect($this->filters->invoke($this->context, 'replace_last', '1 1 1 1', 1, 2))->toBe('1 1 1 2'); + expect($this->filters->invoke($this->context, 'replace_last', '1 1 1 1', 2, 3))->toBe('1 1 1 1'); assertTemplateResult( '1 1 1 2', "{{ '1 1 1 1' | replace_last: '1', 2 }}", @@ -442,19 +444,19 @@ }); test('remove', function () { - expect($this->filters->invoke('remove', 'a a a a', 'a'))->toBe(' '); + expect($this->filters->invoke($this->context, 'remove', 'a a a a', 'a'))->toBe(' '); assertTemplateResult( ' ', "{{ '1 1 1 1' | remove: 1 }}", ); - expect($this->filters->invoke('remove_first', 'a b a a', 'a '))->toBe('b a a'); + expect($this->filters->invoke($this->context, 'remove_first', 'a b a a', 'a '))->toBe('b a a'); assertTemplateResult( ' 1 1 1', "{{ '1 1 1 1' | remove_first: 1 }}", ); - expect($this->filters->invoke('remove_last', 'a a b a', ' a'))->toBe('a a b'); + expect($this->filters->invoke($this->context, 'remove_last', 'a a b a', ' a'))->toBe('a a b'); assertTemplateResult( '1 1 1 ', "{{ '1 1 1 1' | remove_last: 1 }}", @@ -595,19 +597,19 @@ }); test('concat', function () { - expect($this->filters->invoke('concat', [1, 2], [3, 4]))->toBe([1, 2, 3, 4]); - expect($this->filters->invoke('concat', [1, 2], ['a']))->toBe([1, 2, 'a']); - expect($this->filters->invoke('concat', [1, 2], [10]))->toBe([1, 2, 10]); + expect($this->filters->invoke($this->context, 'concat', [1, 2], [3, 4]))->toBe([1, 2, 3, 4]); + expect($this->filters->invoke($this->context, 'concat', [1, 2], ['a']))->toBe([1, 2, 'a']); + expect($this->filters->invoke($this->context, 'concat', [1, 2], [10]))->toBe([1, 2, 10]); - expect(fn () => $this->filters->invoke('concat', [1, 2], 10))->toThrow(TypeError::class); + expect(fn () => $this->filters->invoke($this->context, 'concat', [1, 2], 10))->toThrow(TypeError::class); }); test('default', function () { - expect($this->filters->invoke('default', 'foo', 'bar'))->toBe('foo'); - expect($this->filters->invoke('default', null, 'bar'))->toBe('bar'); - expect($this->filters->invoke('default', '', 'bar'))->toBe('bar'); - expect($this->filters->invoke('default', false, 'bar'))->toBe('bar'); - expect($this->filters->invoke('default', [], 'bar'))->toBe('bar'); + expect($this->filters->invoke($this->context, 'default', 'foo', 'bar'))->toBe('foo'); + expect($this->filters->invoke($this->context, 'default', null, 'bar'))->toBe('bar'); + expect($this->filters->invoke($this->context, 'default', '', 'bar'))->toBe('bar'); + expect($this->filters->invoke($this->context, 'default', false, 'bar'))->toBe('bar'); + expect($this->filters->invoke($this->context, 'default', [], 'bar'))->toBe('bar'); assertTemplateResult('bar', "{{ false | default: 'bar' }}"); assertTemplateResult('bar', "{{ drop | default: 'bar' }}", ['drop' => new BooleanDrop(false)]); @@ -615,11 +617,11 @@ }); test('default handle false', function () { - expect($this->filters->invoke('default', 'foo', 'bar', allow_false: true))->toBe('foo'); - expect($this->filters->invoke('default', null, 'bar', allow_false: true))->toBe('bar'); - expect($this->filters->invoke('default', '', 'bar', allow_false: true))->toBe('bar'); - expect($this->filters->invoke('default', false, 'bar', allow_false: true))->toBe(false); - expect($this->filters->invoke('default', [], 'bar', allow_false: true))->toBe('bar'); + expect($this->filters->invoke($this->context, 'default', 'foo', 'bar', allow_false: true))->toBe('foo'); + expect($this->filters->invoke($this->context, 'default', null, 'bar', allow_false: true))->toBe('bar'); + expect($this->filters->invoke($this->context, 'default', '', 'bar', allow_false: true))->toBe('bar'); + expect($this->filters->invoke($this->context, 'default', false, 'bar', allow_false: true))->toBe(false); + expect($this->filters->invoke($this->context, 'default', [], 'bar', allow_false: true))->toBe('bar'); assertTemplateResult('false', "{{ false | default: 'bar', allow_false: true }}"); assertTemplateResult('Nay', "{{ drop | default: 'bar', allow_false: true }}", ['drop' => new BooleanDrop(false)]); @@ -639,8 +641,8 @@ ['handle' => 'delta', 'ok' => true], ]; - expect($this->filters->invoke('where', $input, 'ok', true))->toBe($expectation); - expect($this->filters->invoke('where', $input, 'ok'))->toBe($expectation); + expect($this->filters->invoke($this->context, 'where', $input, 'ok', true))->toBe($expectation); + expect($this->filters->invoke($this->context, 'where', $input, 'ok'))->toBe($expectation); }); test('where string keys', function () { @@ -648,7 +650,7 @@ $expectation = ['beta']; - expect($this->filters->invoke('where', $input, 'be'))->toBe($expectation); + expect($this->filters->invoke($this->context, 'where', $input, 'be'))->toBe($expectation); }); test('where no key set', function () { @@ -664,8 +666,8 @@ ['handle' => 'delta', 'ok' => true], ]; - expect($this->filters->invoke('where', $input, 'ok', true))->toBe($expectation); - expect($this->filters->invoke('where', $input, 'ok'))->toBe($expectation); + expect($this->filters->invoke($this->context, 'where', $input, 'ok', true))->toBe($expectation); + expect($this->filters->invoke($this->context, 'where', $input, 'ok'))->toBe($expectation); }); test('where non boolean value', function () { @@ -675,24 +677,24 @@ ['message' => 'Hallo!', 'language' => 'German'], ]; - expect($this->filters->invoke('where', $input, 'language', 'French'))->toBe([['message' => 'Bonjour!', 'language' => 'French']]); - expect($this->filters->invoke('where', $input, 'language', 'German'))->toBe([['message' => 'Hallo!', 'language' => 'German']]); - expect($this->filters->invoke('where', $input, 'language', 'English'))->toBe([['message' => 'Hello!', 'language' => 'English']]); + expect($this->filters->invoke($this->context, 'where', $input, 'language', 'French'))->toBe([['message' => 'Bonjour!', 'language' => 'French']]); + expect($this->filters->invoke($this->context, 'where', $input, 'language', 'German'))->toBe([['message' => 'Hallo!', 'language' => 'German']]); + expect($this->filters->invoke($this->context, 'where', $input, 'language', 'English'))->toBe([['message' => 'Hello!', 'language' => 'English']]); }); test('where non array map input', function () { - expect($this->filters->invoke('where', ['a' => 'ok'], 'a', 'ok'))->toBe([['a' => 'ok']]); - expect($this->filters->invoke('where', ['a' => 'not ok'], 'a', 'ok'))->toBe([]); + expect($this->filters->invoke($this->context, 'where', ['a' => 'ok'], 'a', 'ok'))->toBe([['a' => 'ok']]); + expect($this->filters->invoke($this->context, 'where', ['a' => 'not ok'], 'a', 'ok'))->toBe([]); }); test('where indexable but non map value', function () { - expect(fn () => $this->filters->invoke('where', 1, 'ok', true))->toThrow(TypeError::class); - expect(fn () => $this->filters->invoke('where', 1, 'ok'))->toThrow(TypeError::class); + expect(fn () => $this->filters->invoke($this->context, 'where', 1, 'ok', true))->toThrow(TypeError::class); + expect(fn () => $this->filters->invoke($this->context, 'where', 1, 'ok'))->toThrow(TypeError::class); }); test('where array of only unindexable values', function () { - expect($this->filters->invoke('where', [null], 'ok', true))->toBe([]); - expect($this->filters->invoke('where', [null], 'ok'))->toBe([]); + expect($this->filters->invoke($this->context, 'where', [null], 'ok', true))->toBe([]); + expect($this->filters->invoke($this->context, 'where', [null], 'ok'))->toBe([]); }); test('where no target value', function () { @@ -703,21 +705,21 @@ ['bar' => true], ]; - expect($this->filters->invoke('where', $input, 'foo'))->toBe([['foo' => true], ['foo' => 'for sure']]); + expect($this->filters->invoke($this->context, 'where', $input, 'foo'))->toBe([['foo' => true], ['foo' => 'for sure']]); }); test('sum with all numbers', function () { $input = [1, 2]; - expect($this->filters->invoke('sum', $input))->toBe(3); - expect(fn () => $this->filters->invoke('sum', $input, 'quantity'))->toThrow(InvalidArgumentException::class); + expect($this->filters->invoke($this->context, 'sum', $input))->toBe(3); + expect(fn () => $this->filters->invoke($this->context, 'sum', $input, 'quantity'))->toThrow(InvalidArgumentException::class); }); test('sum with numeric strings', function () { $input = [1, 2, '3', '4']; - expect($this->filters->invoke('sum', $input))->toBe(10); - expect(fn () => $this->filters->invoke('sum', $input, 'quantity'))->toThrow(InvalidArgumentException::class); + expect($this->filters->invoke($this->context, 'sum', $input))->toBe(10); + expect(fn () => $this->filters->invoke($this->context, 'sum', $input, 'quantity'))->toThrow(InvalidArgumentException::class); }); test('sum with indexable map values', function () { @@ -727,22 +729,22 @@ ['weight' => 4], ]; - expect($this->filters->invoke('sum', $input))->toBe(0); - expect($this->filters->invoke('sum', $input, 'quantity'))->toBe(3); - expect($this->filters->invoke('sum', $input, 'weight'))->toBe(7); - expect($this->filters->invoke('sum', $input, 'subtotal'))->toBe(0); + expect($this->filters->invoke($this->context, 'sum', $input))->toBe(0); + expect($this->filters->invoke($this->context, 'sum', $input, 'quantity'))->toBe(3); + expect($this->filters->invoke($this->context, 'sum', $input, 'weight'))->toBe(7); + expect($this->filters->invoke($this->context, 'sum', $input, 'subtotal'))->toBe(0); }); test('sum with indexable non map values', function () { $input = [1, 2, 'foo', ['quantity' => 3]]; - expect($this->filters->invoke('sum', $input))->toBe(3); + expect($this->filters->invoke($this->context, 'sum', $input))->toBe(3); }); test('sum with unindexable values', function () { $input = [1, true, null, ['quantity' => 2]]; - expect($this->filters->invoke('sum', $input))->toBe(1); + expect($this->filters->invoke($this->context, 'sum', $input))->toBe(1); }); test('sum without property calls to liquid', function () { diff --git a/tests/Integration/Tags/AssignTagTest.php b/tests/Integration/Tags/AssignTagTest.php index 239321d..d362ac1 100644 --- a/tests/Integration/Tags/AssignTagTest.php +++ b/tests/Integration/Tags/AssignTagTest.php @@ -4,6 +4,7 @@ use Keepsuit\Liquid\Exceptions\SyntaxException; use Keepsuit\Liquid\Render\Context; use Keepsuit\Liquid\Render\ResourceLimits; +use Keepsuit\Liquid\TemplateFactory; test('assign with hyphen in variable name', function () { $source = <<<'LIQUID' @@ -66,13 +67,17 @@ }); test('assign score exceeding resource limit from composite object', function () { - $template = parseTemplate("{% assign foo = 'aaaa' | split: '' %}"); + $factory = TemplateFactory::new(); - $context = new Context(rethrowExceptions: true, resourceLimits: new ResourceLimits(assignScoreLimit: 3)); + $template = parseTemplate("{% assign foo = 'aaaa' | split: '' %}", factory: $factory); + + $factory->setResourceLimits(new ResourceLimits(assignScoreLimit: 3)); + $context = $factory->newRenderContext(rethrowExceptions: true); expect(fn () => $template->render($context))->toThrow(ResourceLimitException::class); expect($context->resourceLimits->reached())->toBeTrue(); - $context = new Context(rethrowExceptions: true, resourceLimits: new ResourceLimits(assignScoreLimit: 5)); + $factory->setResourceLimits(new ResourceLimits(assignScoreLimit: 5)); + $context = $factory->newRenderContext(rethrowExceptions: true); expect($template->render($context))->toBe(''); expect($context->resourceLimits->reached())->toBeFalse(); expect($context->resourceLimits->getAssignScore())->toBe(5); diff --git a/tests/Pest.php b/tests/Pest.php index 54145bd..67f7c7a 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,7 +1,6 @@ parse($source, lineNumbers: $lineNumbers); + return $factory->parse($source, lineNumbers: $lineNumbers); } /** @@ -32,16 +31,15 @@ function renderTemplate( array $registers = [], array $partials = [], bool $renderErrors = false, - TemplateFactory $factory = null, + TemplateFactory $factory = new TemplateFactory() ): string { - $template = parseTemplate($template, factory: $factory); + $factory->setFilesystem(new StubFileSystem(partials: $partials)); - $fileSystem = new StubFileSystem(partials: $partials); + $template = $factory->parse($template, lineNumbers: true); - $context = new Context( + $context = $factory->newRenderContext( staticEnvironment: $assigns, rethrowExceptions: ! $renderErrors, - fileSystem: $fileSystem, ); foreach ($registers as $key => $value) { @@ -58,7 +56,7 @@ function assertTemplateResult( array $registers = [], array $partials = [], bool $renderErrors = false, - TemplateFactory $factory = null, + TemplateFactory $factory = new TemplateFactory(), ): void { expect(renderTemplate( template: $template,