Skip to content

Commit 6c5da00

Browse files
erikaraujobrendt
andauthored
fix(view): wrong matched imports in view component slots (#1173)
Co-authored-by: brendt <[email protected]>
1 parent 5f38986 commit 6c5da00

File tree

9 files changed

+97
-60
lines changed

9 files changed

+97
-60
lines changed

src/Tempest/View/src/Parser/TempestViewCompiler.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use function Tempest\Support\arr;
1717
use function Tempest\Support\path;
18+
use function Tempest\Support\str;
1819

1920
final readonly class TempestViewCompiler
2021
{
@@ -49,7 +50,10 @@ public function compile(string|View $view): string
4950
// 5. Compile to PHP
5051
$compiled = $this->compileElements($elements);
5152

52-
return $compiled;
53+
// 6. Cleanup compiled PHP
54+
$cleaned = $this->cleanupCompiled($compiled);
55+
56+
return $cleaned;
5357
}
5458

5559
private function retrieveTemplate(string|View $view): string
@@ -172,4 +176,38 @@ private function compileElements(array $elements): string
172176
->implode(PHP_EOL)
173177
->toString();
174178
}
179+
180+
private function cleanupCompiled(string $compiled): string
181+
{
182+
// Remove strict type declarations
183+
$compiled = str($compiled)->replace('declare(strict_types=1);', '');
184+
185+
// Cleanup and bundle imports
186+
$imports = arr();
187+
188+
$compiled = $compiled->replaceRegex("/^\s*use (function )?.*;/m", function (array $matches) use (&$imports) {
189+
// The import contains escaped slashes, meaning it's a var_exported string; we can ignore those
190+
if (str_contains($matches[0], '\\\\')) {
191+
return $matches[0];
192+
}
193+
194+
$imports[$matches[0]] = $matches[0];
195+
196+
return '';
197+
});
198+
199+
$compiled = $compiled->prepend(
200+
sprintf(
201+
'<?php
202+
%s
203+
?>',
204+
$imports->implode(PHP_EOL),
205+
),
206+
);
207+
208+
// Remove empty PHP blocks
209+
$compiled = $compiled->replaceRegex('/<\?php\s*\?>/', '');
210+
211+
return $compiled->toString();
212+
}
175213
}

src/Tempest/View/src/Renderers/TempestViewRenderer.php

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -49,43 +49,14 @@ public function render(string|View $view): string
4949

5050
$path = $this->viewCache->getCachedViewPath(
5151
path: $view->path,
52-
compiledView: fn () => $this->cleanupCompiled($this->compiler->compile($view)),
52+
compiledView: fn () => $this->compiler->compile($view),
5353
);
5454

5555
$view = $this->processView($view);
5656

5757
return $this->renderCompiled($view, $path);
5858
}
5959

60-
private function cleanupCompiled(string $compiled): string
61-
{
62-
// Remove strict type declarations
63-
$compiled = str($compiled)->replace('declare(strict_types=1);', '');
64-
65-
// Cleanup and bundle imports
66-
$imports = arr();
67-
68-
$compiled = $compiled->replaceRegex("/^\s*use (function )?.*;/m", function (array $matches) use (&$imports) {
69-
$imports[$matches[0]] = $matches[0];
70-
71-
return '';
72-
});
73-
74-
$compiled = $compiled->prepend(
75-
sprintf(
76-
'<?php
77-
%s
78-
?>',
79-
$imports->implode(PHP_EOL),
80-
),
81-
);
82-
83-
// Remove empty PHP blocks
84-
$compiled = $compiled->replaceRegex('/<\?php\s*\?>/', '');
85-
86-
return $compiled->toString();
87-
}
88-
8960
private function processView(View $view): View
9061
{
9162
foreach ($this->viewConfig->viewProcessors as $viewProcessorClass) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<x-test-layout>
2+
<x-slot name="icon">
3+
<x-icon name="ph:eye" class="size-5" />
4+
</x-slot>
5+
Test
6+
</x-test-layout>

tests/Integration/FrameworkIntegrationTestCase.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,12 @@ protected function registerStaticPage(array|string|MethodReflector $action): voi
163163
$reflector,
164164
);
165165
}
166+
167+
protected function assertSnippetsMatch(string $expected, string $actual): void
168+
{
169+
$expected = str_replace([PHP_EOL, ' '], '', $expected);
170+
$actual = str_replace([PHP_EOL, ' '], '', $actual);
171+
172+
$this->assertSame($expected, $actual);
173+
}
166174
}

tests/Integration/View/IconComponentTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Tempest\View\IconConfig;
1515
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
1616

17+
use function Tempest\view;
18+
1719
final class IconComponentTest extends FrameworkIntegrationTestCase
1820
{
1921
protected function setUp(): void
@@ -183,4 +185,26 @@ public function test_with_dynamic_data(): void
183185
$rendered,
184186
);
185187
}
188+
189+
public function test_icon_renders_inside_named_slot_in_a_layout(): void
190+
{
191+
$this->registerViewComponent('x-test-layout', '<x-index><div><x-slot name="icon" /></div><x-slot /></x-index>');
192+
193+
$mockHttpClient = $this->createMock(HttpClient::class);
194+
$mockHttpClient
195+
->expects($this->exactly(1))
196+
->method('get')
197+
->with('https://api.iconify.design/ph/eye.svg')
198+
->willReturn(new GenericResponse(status: Status::OK, body: '<svg></svg>'));
199+
200+
$this->container->register(HttpClient::class, fn () => $mockHttpClient);
201+
202+
$view = view(__DIR__ . '/../../Fixtures/Views/view-with-icon-inside-named-slot.view.php');
203+
$html = $this->render($view);
204+
205+
$this->assertSnippetsMatch(
206+
'<html lang="en"><head><title></title></head><body><div><svg class="size-5"></svg></div>Test</body></html>',
207+
$html,
208+
);
209+
}
186210
}

tests/Integration/View/TempestViewRendererDataPassingTest.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -347,14 +347,6 @@ public function test_echo_in_attributes(): void
347347
);
348348
}
349349

350-
private function assertSnippetsMatch(string $expected, string $actual): void
351-
{
352-
$expected = str_replace([PHP_EOL, ' '], '', $expected);
353-
$actual = str_replace([PHP_EOL, ' '], '', $actual);
354-
355-
$this->assertSame($expected, $actual);
356-
}
357-
358350
public function test_boolean_attributes_in_view_component(): void
359351
{
360352
$this->registerViewComponent('x-test', <<<HTML

tests/Integration/View/TempestViewRendererTest.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -702,12 +702,4 @@ public function test_if_and_foreach_precedence(): void
702702

703703
$this->assertSnippetsMatch('', $html);
704704
}
705-
706-
private function assertSnippetsMatch(string $expected, string $actual): void
707-
{
708-
$expected = str_replace([PHP_EOL, ' '], '', $expected);
709-
$actual = str_replace([PHP_EOL, ' '], '', $actual);
710-
711-
$this->assertSame($expected, $actual);
712-
}
713705
}

tests/Integration/View/ViewComponentTest.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public function test_slots_is_a_reserved_variable(): void
153153

154154
public function test_nested_components(): void
155155
{
156-
$this->assertStringEqualsStringIgnoringLineEndings(
156+
$this->assertSnippetsMatch(
157157
expected: <<<'HTML'
158158
<form action="#" method="post"><div><div><label for="a">a</label><input type="number" name="a" id="a" value></div></div><div><label for="b">b</label><input type="text" name="b" id="b" value></div></form>
159159
HTML,
@@ -839,11 +839,25 @@ public function test_nested_slots(): void
839839
$this->assertSnippetsMatch('<a><b>hi</b></a>', $html);
840840
}
841841

842-
private function assertSnippetsMatch(string $expected, string $actual): void
842+
public function test_nested_slots_with_escaping(): void
843843
{
844-
$expected = str_replace([PHP_EOL, ' '], '', $expected);
845-
$actual = str_replace([PHP_EOL, ' '], '', $actual);
844+
$this->registerViewComponent('x-a', '<a><x-slot /></a>');
845+
$this->registerViewComponent('x-b', <<<'HTML'
846+
<?php
847+
use function \Tempest\get;
848+
use \Tempest\Core\AppConfig;
849+
?>
850+
{{ get(AppConfig::class)->environment->value }}
851+
HTML);
852+
853+
$html = $this->render(<<<'HTML'
854+
<x-a>
855+
<x-slot>
856+
<x-b />
857+
</x-slot>
858+
</x-a>
859+
HTML);
846860

847-
$this->assertSame($expected, $actual);
861+
$this->assertSnippetsMatch('<a>testing</a>', $html);
848862
}
849863
}

tests/Integration/Vite/ViteTagsComponentTest.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,4 @@ public function test_production_entrypoints(): void
203203
],
204204
);
205205
}
206-
207-
private function assertSnippetsMatch(string $expected, string $actual): void
208-
{
209-
$expected = str_replace([PHP_EOL, ' '], '', $expected);
210-
$actual = str_replace([PHP_EOL, ' '], '', $actual);
211-
212-
$this->assertSame($expected, $actual);
213-
}
214206
}

0 commit comments

Comments
 (0)