From 8353eebeed154969fe42df186888bb41b8851a77 Mon Sep 17 00:00:00 2001 From: vojtechrichter Date: Tue, 1 Sep 2026 16:30:05 +0200 Subject: [PATCH] fix(NAssetNode): generate a unique temp var for n:asset macros --- src/Bridges/AssetsLatte/Nodes/NAssetNode.php | 9 ++- tests/Bridges.Latte/n-asset-n-class.phpt | 82 ++++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 tests/Bridges.Latte/n-asset-n-class.phpt diff --git a/src/Bridges/AssetsLatte/Nodes/NAssetNode.php b/src/Bridges/AssetsLatte/Nodes/NAssetNode.php index 639df4f..c05091e 100644 --- a/src/Bridges/AssetsLatte/Nodes/NAssetNode.php +++ b/src/Bridges/AssetsLatte/Nodes/NAssetNode.php @@ -32,6 +32,7 @@ final class NAssetNode extends StatementNode public ArrayNode $attributes; public AreaNode $content; public bool $optional; + private string $tmpVar; /** @return \Generator, array{AreaNode, ?Tag}, static> */ @@ -56,12 +57,15 @@ public static function create(Tag $tag): \Generator public function print(PrintContext $context): string { + $this->tmpVar = '$ʟ_asset_' . $context->generateId(); + return $context->format( <<<'XX' - if ($ʟ_tmp = $this->global->assets->resolve(%node, %node, %dump)) %line { + if (%raw = $this->global->assets->resolve(%node, %node, %dump)) %line { %node } XX, + $this->tmpVar, $this->name, $this->attributes, $this->optional, @@ -77,8 +81,9 @@ private function init(Tag $tag): void assert($el !== null); $tag->replaceNAttribute(new AuxiliaryNode(fn(PrintContext $context) => $context->format( <<<'XX' - echo $this->global->assets->renderAttributes($ʟ_tmp, %dump, %dump); + echo $this->global->assets->renderAttributes(%raw, %dump, %dump); XX, + $this->tmpVar, strtolower($el->name), self::findUsedAttributes($el), ))); diff --git a/tests/Bridges.Latte/n-asset-n-class.phpt b/tests/Bridges.Latte/n-asset-n-class.phpt new file mode 100644 index 0000000..499ccda --- /dev/null +++ b/tests/Bridges.Latte/n-asset-n-class.phpt @@ -0,0 +1,82 @@ +expects()->getAsset('foo', [])->andReturn($asset); + + $latte = new Latte\Engine; + $latte->addExtension(new LatteExtension($mockRegistry)); + $latte->setLoader(new Latte\Loaders\StringLoader); + + $result = $latte->renderToString(''); + Assert::same('', $result); +}); + + +test('n:asset before n:class on the same element does not clobber the resolved asset', function () { + $asset = new ImageAsset( + url: 'https://example.com/image.jpg', + width: 800, + height: 600, + ); + + $mockRegistry = Mockery::mock(Registry::class); + $mockRegistry->expects()->getAsset('foo', [])->andReturn($asset); + + $latte = new Latte\Engine; + $latte->addExtension(new LatteExtension($mockRegistry)); + $latte->setLoader(new Latte\Loaders\StringLoader); + + $result = $latte->renderToString(''); + Assert::same('', $result); +}); + + +test('multiple n:asset + n:class elements in one template each keep their own asset', function () { + $first = new ImageAsset(url: 'https://example.com/first.jpg', width: 100, height: 100); + $second = new ImageAsset(url: 'https://example.com/second.jpg', width: 200, height: 200); + + $mockRegistry = Mockery::mock(Registry::class); + $mockRegistry->expects()->getAsset('first', [])->andReturn($first); + $mockRegistry->expects()->getAsset('second', [])->andReturn($second); + + $latte = new Latte\Engine; + $latte->addExtension(new LatteExtension($mockRegistry)); + $latte->setLoader(new Latte\Loaders\StringLoader); + + $result = $latte->renderToString( + '', + ); + Assert::same( + '' + . '', + $result, + ); +}); + + +test('n:asset? combined with n:class still renders when the asset is missing', function () { + $mockRegistry = Mockery::mock(Registry::class); + $mockRegistry->expects()->tryGetAsset('foo', [])->andReturn(null); + + $latte = new Latte\Engine; + $latte->addExtension(new LatteExtension($mockRegistry)); + $latte->setLoader(new Latte\Loaders\StringLoader); + + $result = $latte->renderToString(''); + Assert::same('', $result); +});