Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/debug/src/Debug.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

namespace Tempest\Debug;

use Exception;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\VarDumper;
use Tempest\Container\GenericContainer;
use Tempest\EventBus\EventBus;
use Tempest\Highlight\Themes\TerminalStyle;
use Tempest\Support\Filesystem;
use Throwable;

final readonly class Debug
{
Expand All @@ -27,7 +27,7 @@ public static function resolve(): self
config: GenericContainer::instance()->get(DebugConfig::class),
eventBus: GenericContainer::instance()->get(EventBus::class),
);
} catch (Exception) {
} catch (Throwable) {
return new self();
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/view/src/Elements/ElementFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ private function makeElement(Token $token, ?Element $parent): ?Element
return new TextElement(text: $text);
}

if ($token->type === TokenType::WHITESPACE) {
return new WhitespaceElement($token->content);
}

if (! $token->tag || $token->type === TokenType::COMMENT || $token->type === TokenType::PHP) {
return new RawElement(token: $token, tag: null, content: $token->compile());
}
Expand Down
4 changes: 4 additions & 0 deletions packages/view/src/Elements/IsElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ public function setPrevious(?Element $previous): self

public function getPrevious(): ?Element
{
if ($this->previous instanceof WhitespaceElement) {
return $this->previous->getPrevious();
}

return $this->previous;
}

Expand Down
21 changes: 21 additions & 0 deletions packages/view/src/Elements/WhitespaceElement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Tempest\View\Elements;

use Tempest\View\Element;

final class WhitespaceElement implements Element
{
use IsElement;

public function __construct(
public string $content,
) {}

public function compile(): string
{
return $this->content;
}
}
19 changes: 19 additions & 0 deletions packages/view/src/Parser/TempestViewLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public function lex(): TokenCollection
$tokens = [...$tokens, ...$this->lexCharacterData()];
} elseif ($this->comesNext('<')) {
$tokens = [...$tokens, ...$this->lexTag()];
} elseif ($this->comesNext(' ') || $this->comesNext(PHP_EOL)) {
$tokens[] = $this->lexWhitespace();
} else {
$tokens[] = $this->lexContent();
}
Expand Down Expand Up @@ -214,6 +216,23 @@ private function lexDoctype(): Token
return new Token($buffer, TokenType::DOCTYPE);
}

private function lexWhitespace(): Token
{
$buffer = '';

while ($this->current !== null) {
$seek = $this->seek();

if ($seek !== ' ' && $seek !== PHP_EOL) {
break;
}

$buffer .= $this->consume();
}

return new Token($buffer, TokenType::WHITESPACE);
}

private function lexCharacterData(): array
{
$tokens = [
Expand Down
1 change: 1 addition & 0 deletions packages/view/src/Parser/TokenType.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ enum TokenType
case DOCTYPE;
case CHARACTER_DATA_OPEN;
case CHARACTER_DATA_CLOSE;
case WHITESPACE;
}
4 changes: 2 additions & 2 deletions packages/view/tests/FallthroughAttributesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ public function render(): void
view(__DIR__ . '/Fixtures/fallthrough.view.php'),
);

$this->assertEquals(<<<'HTML'
$this->assertEquals(str_replace([' ', PHP_EOL], '', <<<'HTML'
<div class="in-component component-class"></div>
<div class="in-component component-class"></div>
<div class="component-class" style="display: block;"></div>
<div class="component-class" style="display: block;"></div>
HTML, $html);
HTML), str_replace([' ', PHP_EOL], '', $html));
}
}
29 changes: 28 additions & 1 deletion packages/view/tests/TempestViewLexerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,40 @@ class=', TokenType::ATTRIBUTE_NAME),
new Token("\n>", TokenType::OPEN_TAG_END),
new Token('

', TokenType::CONTENT),
', TokenType::WHITESPACE),
new Token('</div>', TokenType::CLOSING_TAG),
],
actual: $tokens,
);
}

public function test_whitespace(): void
{
$html = <<<'HTML'
<p><strong>Test</strong> <em>Test</em></p>
HTML;

$tokens = new TempestViewLexer($html)->lex();

$this->assertTokens(
expected: [
new Token('<p', TokenType::OPEN_TAG_START),
new Token('>', TokenType::OPEN_TAG_END),
new Token('<strong', TokenType::OPEN_TAG_START),
new Token('>', TokenType::OPEN_TAG_END),
new Token('Test', TokenType::CONTENT),
new Token('</strong>', TokenType::CLOSING_TAG),
new Token(' ', TokenType::WHITESPACE),
new Token('<em', TokenType::OPEN_TAG_START),
new Token('>', TokenType::OPEN_TAG_END),
new Token('Test', TokenType::CONTENT),
new Token('</em>', TokenType::CLOSING_TAG),
new Token('</p>', TokenType::CLOSING_TAG),
],
actual: $tokens,
);
}

public function test_lexer_with_falsy_values(): void
{
$html = <<<'HTML'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ public function query(): void
#[Test]
public function raw_body_string(): void
{
$this->registerRoute([TestController::class, 'handleRawBody']);
$this->http->registerRoute([TestController::class, 'handleRawBody']);

$response = $this->http
->post('/raw-body', body: 'ok')
Expand Down
32 changes: 22 additions & 10 deletions tests/Integration/View/ElementFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

namespace Tests\Tempest\Integration\View;

use Tempest\View\Element;
use Tempest\View\Elements\ElementFactory;
use Tempest\View\Elements\GenericElement;
use Tempest\View\Elements\TextElement;
use Tempest\View\Elements\WhitespaceElement;
use Tempest\View\Parser\TempestViewParser;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;

use function Tempest\Support\arr;

/**
* @internal
*/
Expand Down Expand Up @@ -37,33 +41,41 @@ public function test_parental_relations(): void
$a = $elementFactory->make(iterator_to_array($ast)[0]);

$this->assertInstanceOf(GenericElement::class, $a);
$this->assertCount(1, $a->getChildren());
$this->assertCount(1, $this->withoutWhitespace($a->getChildren()));
$this->assertNull($a->getParent());

$b = $a->getChildren()[0];
$b = $this->withoutWhitespace($a->getChildren())[0];
$this->assertInstanceOf(GenericElement::class, $b);
$this->assertCount(3, $b->getChildren());
$this->assertCount(3, $this->withoutWhitespace($b->getChildren()));
$this->assertSame($b->getParent(), $a);

$c = $b->getChildren()[0];
$c = $this->withoutWhitespace($b->getChildren())[0];
$this->assertInstanceOf(GenericElement::class, $c);
$this->assertCount(1, $c->getChildren());
$this->assertCount(1, $this->withoutWhitespace($c->getChildren()));
$this->assertSame($c->getParent(), $b);

$text = $c->getChildren()[0];
$text = $this->withoutWhitespace($c->getChildren())[0];
$this->assertInstanceOf(TextElement::class, $text);
$this->assertSame($text->getParent(), $c);

$d = $b->getChildren()[1];
$d = $this->withoutWhitespace($b->getChildren())[1];
$this->assertInstanceOf(GenericElement::class, $d);
$this->assertCount(0, $d->getChildren());
$this->assertCount(0, $this->withoutWhitespace($d->getChildren()));
$this->assertSame($d->getParent(), $b);
$this->assertSame($d->getPrevious(), $c);

$e = $b->getChildren()[2];
$e = $this->withoutWhitespace($b->getChildren())[2];
$this->assertInstanceOf(GenericElement::class, $e);
$this->assertCount(0, $e->getChildren());
$this->assertCount(0, $this->withoutWhitespace($e->getChildren()));
$this->assertSame($e->getParent(), $b);
$this->assertSame($e->getPrevious(), $d);
}

private function withoutWhitespace(array $elements): array
{
return arr($elements)
->filter(fn (Element $element) => ! $element instanceof WhitespaceElement)
->values()
->toArray();
}
}
41 changes: 40 additions & 1 deletion tests/Integration/View/TempestViewRendererTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ public function test_default_slot(): void

public function test_implicit_default_slot(): void
{
$this->assertStringEqualsStringIgnoringLineEndings(
$this->assertSnippetsMatch(
<<<'HTML'
<div class="base">

Expand Down Expand Up @@ -891,4 +891,43 @@ public function test_discovery_locations_are_passed_to_compiler(): void

$this->assertSnippetsMatch('<div>Hi</div>', $html);
}

public function test_whitespace_between_inline_elements_is_preserved(): void
{
/** @var TempestViewRenderer $renderer */
$renderer = $this->get(TempestViewRenderer::class);

$this->assertSame(
'<p><strong>Test</strong> <em>Test</em></p>',
$renderer->render('<p><strong>Test</strong> <em>Test</em></p>'),
);
}

public function test_whitespace_introduced_by_line_breaks_is_preserved(): void
{
/** @var TempestViewRenderer $renderer */
$renderer = $this->get(TempestViewRenderer::class);

$this->assertSame(
'<p><strong>Test</strong>
<em>Test</em></p>',
$renderer->render('<p><strong>Test</strong>
<em>Test</em></p>'),
);
}

public function test_whitespace_with_blank_lines_between_inline_elements_is_preserved(): void
{
/** @var TempestViewRenderer $renderer */
$renderer = $this->get(TempestViewRenderer::class);

$this->assertSame(
'<p><strong>Test</strong>

<em>Test</em></p>',
$renderer->render('<p><strong>Test</strong>

<em>Test</em></p>'),
);
}
}
27 changes: 17 additions & 10 deletions tests/Integration/View/ViewComponentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -471,14 +471,14 @@ public static function view_components(): Generator
public function test_full_html_document_as_component(): void
{
$this->view->registerViewComponent('x-layout', <<<'HTML'
<html lang="en">
<head>
<title>Tempest View</title>
</head>
<body>
<x-slot />
</body>
</html>
<html lang="en">
<head>
<title>Tempest View</title>
</head>
<body>
<x-slot />
</body>
</html>
HTML);

$html = $this->view->render(<<<'HTML'
Expand All @@ -487,9 +487,16 @@ public function test_full_html_document_as_component(): void
</x-layout>
HTML);

$this->assertStringContainsString('<html lang="en"><head><title>Tempest View</title></head><body>', $html);
$this->assertStringContainsString(<<<'HTML'
<html lang="en">
<head>
<title>Tempest View</title>
</head>
<body>
HTML, $html);
$this->assertStringContainsString('Hello World', $html);
$this->assertStringContainsString('</body></html>', $html);
$this->assertStringContainsString('</body>', $html);
$this->assertStringContainsString('</html>', $html);
}

public function test_empty_slots_are_commented_out(): void
Expand Down