From cd2072e4090d2afcbb98f4e461a1a2c32ee9389a Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 14:22:59 -0400 Subject: [PATCH 01/30] update mikecao copyright --- composer.json | 2 +- flight/template/View.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 70a09ca5..6a036543 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ { "name": "Mike Cao", "email": "mike@mikecao.com", - "homepage": "http://www.mikecao.com/", + "homepage": "https://mikecao.com", "role": "Original Developer" }, { diff --git a/flight/template/View.php b/flight/template/View.php index f995d365..e14b0f4d 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -9,8 +9,8 @@ * methods for managing view data and inserts the data into * view templates upon rendering. * - * @license MIT, http://flightphp.com/license - * @copyright Copyright (c) 2011, Mike Cao + * @license MIT, https://docs.flightphp.com/license + * @copyright 2011 [Mike Cao](https://mikecao.com) */ class View { From a3423b4cdf4aabf0d73f927f4ada88ee013a29a8 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 14:25:34 -0400 Subject: [PATCH 02/30] update license docblock --- flight/template/View.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flight/template/View.php b/flight/template/View.php index e14b0f4d..33660978 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -9,7 +9,7 @@ * methods for managing view data and inserts the data into * view templates upon rendering. * - * @license MIT, https://docs.flightphp.com/license + * @license [MIT](https://docs.flightphp.com/license) * @copyright 2011 [Mike Cao](https://mikecao.com) */ class View From 98e20c6ad18c10cc16419d70805171fbd56e5fb1 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 18:31:37 -0400 Subject: [PATCH 03/30] import Exception --- flight/template/View.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flight/template/View.php b/flight/template/View.php index 33660978..957acbfc 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -4,6 +4,8 @@ namespace flight\template; +use Exception; + /** * The View class represents output to be displayed. It provides * methods for managing view data and inserts the data into @@ -105,7 +107,7 @@ public function clear(?string $key = null): self * @param string $file Template file * @param ?array $templateData Template data * - * @throws \Exception If template not found + * @throws Exception If template not found */ public function render(string $file, ?array $templateData = null): void { @@ -113,7 +115,7 @@ public function render(string $file, ?array $templateData = null): void if (!\file_exists($this->template)) { $normalized_path = self::normalizePath($this->template); - throw new \Exception("Template file not found: {$normalized_path}."); + throw new Exception("Template file not found: $normalized_path."); } \extract($this->vars); From f047d22fab1a82c761a55977663f65610b21a7a0 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 18:34:31 -0400 Subject: [PATCH 04/30] simplify docblocks --- flight/template/View.php | 45 +++++++++++----------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/flight/template/View.php b/flight/template/View.php index 957acbfc..5e8b5168 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -16,37 +16,28 @@ */ class View { - /** Location of view templates. */ + /** Location of view templates */ public string $path; - /** File extension. */ + /** File extension */ public string $extension = '.php'; public bool $preserveVars = true; - /** - * View variables. - * - * @var array $vars - */ + /** @var array View variables */ protected array $vars = []; /** Template file. */ private string $template; - /** - * Constructor. - * - * @param string $path Path to templates directory - */ + /** @param string $path Path to templates directory */ public function __construct(string $path = '.') { $this->path = $path; } /** - * Gets a template variable. - * + * Gets a template variable * @return mixed Variable value or `null` if doesn't exists */ public function get(string $key) @@ -76,8 +67,7 @@ public function set($key, $value = null): self } /** - * Checks if a template variable is set. - * + * Checks if a template variable is set * @return bool If key exists */ public function has(string $key): bool @@ -86,8 +76,7 @@ public function has(string $key): bool } /** - * Unsets a template variable. If no key is passed in, clear all variables. - * + * Unsets a template variable. If no key is passed in, clear all variables * @return $this */ public function clear(?string $key = null): self @@ -102,11 +91,9 @@ public function clear(?string $key = null): self } /** - * Renders a template. - * + * Renders a template * @param string $file Template file * @param ?array $templateData Template data - * * @throws Exception If template not found */ public function render(string $file, ?array $templateData = null): void @@ -132,11 +119,9 @@ public function render(string $file, ?array $templateData = null): void } /** - * Gets the output of a template. - * + * Gets the output of a template * @param string $file Template file * @param ?array $data Template data - * * @return string Output of template */ public function fetch(string $file, ?array $data = null): string @@ -149,10 +134,8 @@ public function fetch(string $file, ?array $data = null): string } /** - * Checks if a template file exists. - * + * Checks if a template file exists * @param string $file Template file - * * @return bool Template file exists */ public function exists(string $file): bool @@ -161,10 +144,8 @@ public function exists(string $file): bool } /** - * Gets the full path to a template file. - * + * Gets the full path to a template file * @param string $file Template file - * * @return string Template file location */ public function getTemplate(string $file): string @@ -185,10 +166,8 @@ public function getTemplate(string $file): string } /** - * Displays escaped output. - * + * Displays escaped output * @param string $str String to escape - * * @return string Escaped string */ public function e(string $str): string From 7d29fdf7195ee70966dcd1f3d4a2399cbcd5fafd Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 18:35:32 -0400 Subject: [PATCH 05/30] use $this instead of self --- flight/template/View.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/flight/template/View.php b/flight/template/View.php index 5e8b5168..51ed7c78 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -50,10 +50,9 @@ public function get(string $key) * * @param string|iterable $key * @param mixed $value Value - * - * @return self + * @return $this */ - public function set($key, $value = null): self + public function set($key, $value = null) { if (\is_iterable($key)) { foreach ($key as $k => $v) { @@ -79,7 +78,7 @@ public function has(string $key): bool * Unsets a template variable. If no key is passed in, clear all variables * @return $this */ - public function clear(?string $key = null): self + public function clear(?string $key = null) { if ($key === null) { $this->vars = []; From d28939f2044fb83eb5fe6ad9664e017dc946c900 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 18:36:12 -0400 Subject: [PATCH 06/30] replace iterable for arrays --- flight/template/View.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/flight/template/View.php b/flight/template/View.php index 51ed7c78..c5c57888 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -46,15 +46,14 @@ public function get(string $key) } /** - * Sets a template variable. - * - * @param string|iterable $key + * Sets a template variable + * @param string|array $key * @param mixed $value Value * @return $this */ public function set($key, $value = null) { - if (\is_iterable($key)) { + if (is_array($key)) { foreach ($key as $k => $v) { $this->vars[$k] = $v; } From 206eff4f3df9a19d578e1c4840458d733f8c137a Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 18:37:37 -0400 Subject: [PATCH 07/30] simplify render --- flight/template/View.php | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/flight/template/View.php b/flight/template/View.php index c5c57888..598a3956 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -27,9 +27,6 @@ class View /** @var array View variables */ protected array $vars = []; - /** Template file. */ - private string $template; - /** @param string $path Path to templates directory */ public function __construct(string $path = '.') { @@ -96,24 +93,25 @@ public function clear(?string $key = null) */ public function render(string $file, ?array $templateData = null): void { - $this->template = $this->getTemplate($file); + $template = $this->getTemplate($file); + + if (!$this->exists($file)) { + $normalized_path = $this::normalizePath($template); - if (!\file_exists($this->template)) { - $normalized_path = self::normalizePath($this->template); throw new Exception("Template file not found: $normalized_path."); } - \extract($this->vars); + extract($this->vars); - if (\is_array($templateData) === true) { - \extract($templateData); + if (is_array($templateData)) { + extract($templateData); - if ($this->preserveVars === true) { - $this->vars = \array_merge($this->vars, $templateData); + if ($this->preserveVars) { + $this->vars += $templateData; } } - include $this->template; + include $template; } /** From 2321b0e079bbc310592c900d48096a5e39632749 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 18:38:10 -0400 Subject: [PATCH 08/30] simplify getTemplate --- flight/template/View.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/flight/template/View.php b/flight/template/View.php index 598a3956..92a2d1b2 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -146,15 +146,13 @@ public function exists(string $file): bool */ public function getTemplate(string $file): string { - $ext = $this->extension; - - if (!empty($ext) && (\substr($file, -1 * \strlen($ext)) != $ext)) { - $file .= $ext; + if (!empty($this->extension) && (substr($file, -1 * strlen($this->extension)) !== $this->extension)) { + $file .= $this->extension; } - $is_windows = \strtoupper(\substr(PHP_OS, 0, 3)) === 'WIN'; + $is_windows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; - if ((\substr($file, 0, 1) === '/') || ($is_windows && \substr($file, 1, 1) === ':')) { + if ((substr($file, 0, 1) === '/') || ($is_windows && substr($file, 1, 1) === ':')) { return $file; } From abdf84589968594e598fd5adaa69ec5e56edb1ad Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 18:38:34 -0400 Subject: [PATCH 09/30] remove \ from global function calls --- flight/template/View.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/flight/template/View.php b/flight/template/View.php index 92a2d1b2..63f2a8d5 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -122,11 +122,11 @@ public function render(string $file, ?array $templateData = null): void */ public function fetch(string $file, ?array $data = null): string { - \ob_start(); + ob_start(); $this->render($file, $data); - return \ob_get_clean(); + return ob_get_clean(); } /** @@ -136,7 +136,7 @@ public function fetch(string $file, ?array $data = null): string */ public function exists(string $file): bool { - return \file_exists($this->getTemplate($file)); + return file_exists($this->getTemplate($file)); } /** @@ -166,13 +166,16 @@ public function getTemplate(string $file): string */ public function e(string $str): string { - $value = \htmlentities($str); + $value = htmlentities($str); echo $value; + return $value; } - protected static function normalizePath(string $path, string $separator = DIRECTORY_SEPARATOR): string - { - return \str_replace(['\\', '/'], $separator, $path); + protected static function normalizePath( + string $path, + string $separator = DIRECTORY_SEPARATOR + ): string { + return str_replace(['\\', '/'], $separator, $path); } } From 6b506636f05d3027c6262a09a50afe3afe537270 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 18:56:18 -0400 Subject: [PATCH 10/30] always normalize paths --- flight/template/View.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/flight/template/View.php b/flight/template/View.php index 63f2a8d5..26c3f2ca 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -96,9 +96,7 @@ public function render(string $file, ?array $templateData = null): void $template = $this->getTemplate($file); if (!$this->exists($file)) { - $normalized_path = $this::normalizePath($template); - - throw new Exception("Template file not found: $normalized_path."); + throw new Exception("Template file not found: $template."); } extract($this->vars); @@ -156,7 +154,7 @@ public function getTemplate(string $file): string return $file; } - return $this->path . DIRECTORY_SEPARATOR . $file; + return $this::normalizePath("$this->path/$file"); } /** From b1c56b1a6e8d4c99a761b1749e254dc8dcdcd363 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 19:28:10 -0400 Subject: [PATCH 11/30] remove final_newlines to all tests/views/* --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index d3de6486..d6b66b88 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,5 +11,5 @@ insert_final_newline = true [*.md] indent_size = 2 -[tests/views/*.php] +[tests/views/*] insert_final_newline = false From ee9699e8ad9bd92764c3fdd149af34164fce0ea6 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 19:29:27 -0400 Subject: [PATCH 12/30] simplify view instantiation --- tests/ViewTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ViewTest.php b/tests/ViewTest.php index e57afd09..6a03320d 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -14,8 +14,7 @@ class ViewTest extends TestCase protected function setUp(): void { - $this->view = new View(); - $this->view->path = __DIR__ . '/views'; + $this->view = new View(__DIR__ . '/views'); } // Set template variables From 6afd221c76b310871003f080037290f9c0e821b3 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 19:30:18 -0400 Subject: [PATCH 13/30] remove ViewTest methods comments --- tests/ViewTest.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 6a03320d..2ead7fe5 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -17,7 +17,6 @@ protected function setUp(): void $this->view = new View(__DIR__ . '/views'); } - // Set template variables public function testVariables(): void { $this->view->set('test', 123); @@ -48,14 +47,12 @@ public function testMultipleVariables(): void $this->assertNull($this->view->get('foo')); } - // Check if template files exist public function testTemplateExists(): void { $this->assertTrue($this->view->exists('hello.php')); $this->assertTrue(!$this->view->exists('unknown.php')); } - // Render a template public function testRender(): void { $this->view->render('hello', ['name' => 'Bob']); @@ -77,7 +74,6 @@ public function testRenderBadFilePath(): void $this->view->render('badfile'); } - // Fetch template output public function testFetch(): void { $output = $this->view->fetch('hello', ['name' => 'Bob']); @@ -85,7 +81,6 @@ public function testFetch(): void $this->assertEquals('Hello, Bob!', $output); } - // Default extension public function testTemplateWithExtension(): void { $this->view->set('name', 'Bob'); @@ -95,7 +90,6 @@ public function testTemplateWithExtension(): void $this->expectOutputString('Hello, Bob!'); } - // Custom extension public function testTemplateWithCustomExtension(): void { $this->view->set('name', 'Bob'); From a6dbd321ae44dabda47278d83eee6a1f0c7ce07f Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 19:30:39 -0400 Subject: [PATCH 14/30] use short echo --- tests/views/hello.php | 2 +- tests/views/world.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/views/hello.php b/tests/views/hello.php index 86a9b806..8551a874 100644 --- a/tests/views/hello.php +++ b/tests/views/hello.php @@ -1 +1 @@ -Hello, ! \ No newline at end of file +Hello, ! \ No newline at end of file diff --git a/tests/views/world.html b/tests/views/world.html index f4c856e9..d274ca39 100644 --- a/tests/views/world.html +++ b/tests/views/world.html @@ -1 +1 @@ -Hello world, ! +Hello world, ! \ No newline at end of file From 576801967cb345d32cf33289f44ae7a6262b1579 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Tue, 23 Jun 2026 19:30:51 -0400 Subject: [PATCH 15/30] simplify ViewTest --- tests/ViewTest.php | 135 +++++++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 2ead7fe5..91e7b853 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -21,55 +21,52 @@ public function testVariables(): void { $this->view->set('test', 123); - $this->assertEquals(123, $this->view->get('test')); - - $this->assertTrue($this->view->has('test')); - $this->assertTrue(!$this->view->has('unknown')); + self::assertSame(123, $this->view->get('test')); + self::assertTrue($this->view->has('test')); + self::assertFalse($this->view->has('unknown')); $this->view->clear('test'); - $this->assertNull($this->view->get('test')); + self::assertNull($this->view->get('test')); } public function testMultipleVariables(): void { - $this->view->set([ - 'test' => 123, - 'foo' => 'bar' - ]); + $this->view->set(['test' => 123, 'foo' => 'bar']); - $this->assertEquals(123, $this->view->get('test')); - $this->assertEquals('bar', $this->view->get('foo')); + self::assertSame(123, $this->view->get('test')); + self::assertSame('bar', $this->view->get('foo')); $this->view->clear(); - $this->assertNull($this->view->get('test')); - $this->assertNull($this->view->get('foo')); + self::assertNull($this->view->get('test')); + self::assertNull($this->view->get('foo')); } public function testTemplateExists(): void { - $this->assertTrue($this->view->exists('hello.php')); - $this->assertTrue(!$this->view->exists('unknown.php')); + self::assertTrue($this->view->exists('hello.php')); + self::assertFalse($this->view->exists('unknown.php')); } public function testRender(): void { $this->view->render('hello', ['name' => 'Bob']); - $this->expectOutputString('Hello, Bob!'); + self::expectOutputString('Hello, Bob!'); } public function testRenderBadFilePath(): void { - $this->expectException(Exception::class); $exception_message = sprintf( 'Template file not found: %s%sviews%sbadfile.php', __DIR__, DIRECTORY_SEPARATOR, - DIRECTORY_SEPARATOR + DIRECTORY_SEPARATOR, ); - $this->expectExceptionMessage($exception_message); + + self::expectException(Exception::class); + self::expectExceptionMessage($exception_message); $this->view->render('badfile'); } @@ -78,30 +75,22 @@ public function testFetch(): void { $output = $this->view->fetch('hello', ['name' => 'Bob']); - $this->assertEquals('Hello, Bob!', $output); + self::assertSame('Hello, Bob!', $output); } public function testTemplateWithExtension(): void { - $this->view->set('name', 'Bob'); + $this->view->render('hello.php', ['name' => 'Bob']); - $this->view->render('hello.php'); - - $this->expectOutputString('Hello, Bob!'); + self::expectOutputString('Hello, Bob!'); } public function testTemplateWithCustomExtension(): void { - $this->view->set('name', 'Bob'); - $this->view->extension = '.html'; - - ob_start(); - $this->view->render('world'); - $html = ob_get_clean(); - $html = str_replace(["\r\n", "\n"], '', $html); - echo $html; + self::expectOutputString('Hello world, Bob!'); - $this->expectOutputString("Hello world, Bob!"); + $this->view->extension = '.html'; + $this->view->render('world', ['name' => 'Bob']); } public function testGetTemplateAbsolutePath(): void @@ -109,48 +98,64 @@ public function testGetTemplateAbsolutePath(): void $tmpfile = tmpfile(); $this->view->extension = ''; $file_path = stream_get_meta_data($tmpfile)['uri']; - $this->assertEquals($file_path, $this->view->getTemplate($file_path)); + + self::assertSame($file_path, $this->view->getTemplate($file_path)); } public function testE(): void { - $this->expectOutputString('<script>'); + $expectedString = '<script>'; + + self::expectOutputString($expectedString); + $result = $this->view->e(' + html; + } + + ob_end_clean(); + + break; + default: + $view = ob_get_clean(); + } + preg_match('/[a-z-]+)\s*\/>/', $view, $matches); if ($matches) { diff --git a/tests/ViewTest.php b/tests/ViewTest.php index d71e1662..b649a563 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -239,24 +239,82 @@ public function testItRendersComponent(string $page, string $expected): void { $view = new View(__DIR__ . '/views'); $view->preserveVars = false; - $actual = $view->fetch($page); + $actual = $view->fetch("pages/$page"); - self::assertSame(self::removeLineEndings($expected), self::removeLineEndings($actual)); + self::assertSame( + self::removeIndentation(self::removeLineEndings($expected)), + self::removeIndentation(self::removeLineEndings($actual)), + ); } public static function pagesDataProvider(): array { return [ - ['pages/page-with-component-with-old-syntax', <<<'html' - - value - - html], - ['pages/page-with-component-with-new-syntax', <<<'html' - - value - - html], + [ + 'page-with-component-with-old-syntax', + <<<'html' + my-component + html, + ], + [ + 'page-with-component-with-new-syntax', + <<<'html' + my-component + html, + ], + [ + 'page-with-component-with-subcomponent', + <<<'html' +
+ my-component-with-subcomponent + subcomponent +
+ html, + ], + [ + 'page-with-multiple-components', + <<<'html' +
    +
  • my-component
  • +
  • my-component
  • +
+ html, + ], + [ + 'page-with-functional-component', + <<<'html' + my-functional-component + html, + ], + [ + 'page-with-class-component', + <<<'html' + my-class-component + html, + ], + [ + 'page-with-class-component-with-styles', + <<<'html' + + my-class-component-with-styles + + + + html, + ], + [ + 'page-with-class-component-with-scripts', + <<<'html' + my-class-component-with-scripts + + + html, + ], + ]; } @@ -264,4 +322,9 @@ private static function removeLineEndings(string $subject): string { return str_replace(["\r", "\n"], '', $subject); } + + private static function removeIndentation(string $subject): string + { + return str_replace(' ', '', $subject); + } } diff --git a/tests/views/components/my-class-component-with-scripts.php b/tests/views/components/my-class-component-with-scripts.php new file mode 100644 index 00000000..280307e6 --- /dev/null +++ b/tests/views/components/my-class-component-with-scripts.php @@ -0,0 +1,24 @@ + + my-class-component-with-styles + + html; + } + + #[Override] + public function css(): string + { + return <<<'css' + .my-class-component-with-styles { + color: red; + } + css; + } +}; \ No newline at end of file diff --git a/tests/views/components/my-class-component.php b/tests/views/components/my-class-component.php new file mode 100644 index 00000000..be86579f --- /dev/null +++ b/tests/views/components/my-class-component.php @@ -0,0 +1,16 @@ + + my-component-with-subcomponent + + \ No newline at end of file diff --git a/tests/views/components/my-component.php b/tests/views/components/my-component.php index 6d4e1507..e3ecf456 100644 --- a/tests/views/components/my-component.php +++ b/tests/views/components/my-component.php @@ -1 +1 @@ -value +my-component \ No newline at end of file diff --git a/tests/views/components/my-functional-component.php b/tests/views/components/my-functional-component.php new file mode 100644 index 00000000..0bff9490 --- /dev/null +++ b/tests/views/components/my-functional-component.php @@ -0,0 +1,7 @@ + <<<'html' +my-functional-component +html; \ No newline at end of file diff --git a/tests/views/components/subcomponent.php b/tests/views/components/subcomponent.php new file mode 100644 index 00000000..8aca66b3 --- /dev/null +++ b/tests/views/components/subcomponent.php @@ -0,0 +1 @@ +subcomponent \ No newline at end of file diff --git a/tests/views/pages/page-with-class-component-with-scripts.php b/tests/views/pages/page-with-class-component-with-scripts.php new file mode 100644 index 00000000..0fcd56fc --- /dev/null +++ b/tests/views/pages/page-with-class-component-with-scripts.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/views/pages/page-with-class-component-with-styles.php b/tests/views/pages/page-with-class-component-with-styles.php new file mode 100644 index 00000000..3ca6a359 --- /dev/null +++ b/tests/views/pages/page-with-class-component-with-styles.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/views/pages/page-with-class-component.php b/tests/views/pages/page-with-class-component.php new file mode 100644 index 00000000..76a7423d --- /dev/null +++ b/tests/views/pages/page-with-class-component.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/views/pages/page-with-component-with-new-syntax.php b/tests/views/pages/page-with-component-with-new-syntax.php index f02d75a8..31ebaab3 100644 --- a/tests/views/pages/page-with-component-with-new-syntax.php +++ b/tests/views/pages/page-with-component-with-new-syntax.php @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/tests/views/pages/page-with-component-with-old-syntax.php b/tests/views/pages/page-with-component-with-old-syntax.php index ff5cf50d..1aa97028 100644 --- a/tests/views/pages/page-with-component-with-old-syntax.php +++ b/tests/views/pages/page-with-component-with-old-syntax.php @@ -1,3 +1 @@ - - render('components/my-component') ?> - +render('components/my-component') ?> \ No newline at end of file diff --git a/tests/views/pages/page-with-component-with-subcomponent.php b/tests/views/pages/page-with-component-with-subcomponent.php new file mode 100644 index 00000000..d0328095 --- /dev/null +++ b/tests/views/pages/page-with-component-with-subcomponent.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/views/pages/page-with-functional-component.php b/tests/views/pages/page-with-functional-component.php new file mode 100644 index 00000000..07fcec24 --- /dev/null +++ b/tests/views/pages/page-with-functional-component.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/views/pages/page-with-multiple-components.php b/tests/views/pages/page-with-multiple-components.php new file mode 100644 index 00000000..f7836b44 --- /dev/null +++ b/tests/views/pages/page-with-multiple-components.php @@ -0,0 +1,7 @@ +
    + +
  • + +
  • + +
\ No newline at end of file From bc86e7a29803e987c7da025b0e1e60c153fcec98 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 24 Jun 2026 02:08:11 -0400 Subject: [PATCH 18/30] 3. renders self close class components that extends another class component --- tests/ViewTest.php | 7 ++++++- .../components/another-class-component.php | 19 +++++++++++++++++++ ...t-that-extends-another-class-component.php | 16 ++++++++++++++++ .../my-class-component-with-scripts.php | 4 +--- tests/views/components/my-class-component.php | 4 +--- .../components/my-functional-component.php | 4 +--- ...t-that-extends-another-class-component.php | 1 + 7 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 tests/views/components/another-class-component.php create mode 100644 tests/views/components/my-class-component-that-extends-another-class-component.php create mode 100644 tests/views/pages/page-with-class-component-that-extends-another-class-component.php diff --git a/tests/ViewTest.php b/tests/ViewTest.php index b649a563..a6abff5b 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -314,7 +314,12 @@ public static function pagesDataProvider(): array html, ], - + [ + 'page-with-class-component-that-extends-another-class-component', + <<<'html' + another-class-component extended by my-class-component-that-extends-another-class-component + html, + ], ]; } diff --git a/tests/views/components/another-class-component.php b/tests/views/components/another-class-component.php new file mode 100644 index 00000000..27313737 --- /dev/null +++ b/tests/views/components/another-class-component.php @@ -0,0 +1,19 @@ + <<<'html' -my-functional-component -html; \ No newline at end of file +return fn (): string => 'my-functional-component'; \ No newline at end of file diff --git a/tests/views/pages/page-with-class-component-that-extends-another-class-component.php b/tests/views/pages/page-with-class-component-that-extends-another-class-component.php new file mode 100644 index 00000000..2fef30ab --- /dev/null +++ b/tests/views/pages/page-with-class-component-that-extends-another-class-component.php @@ -0,0 +1 @@ + \ No newline at end of file From 538c9d32ae97d2aad222cdd09b229fecbfa101a6 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 24 Jun 2026 02:44:36 -0400 Subject: [PATCH 19/30] 4. renders self close components with one prop --- flight/template/View.php | 21 +++++++++++++++++-- tests/ViewTest.php | 10 +++++++++ tests/views/components/greeting.php | 1 + .../page-with-component-with-one-prop.php | 5 +++++ 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 tests/views/components/greeting.php create mode 100644 tests/views/pages/page-with-component-with-one-prop.php diff --git a/flight/template/View.php b/flight/template/View.php index 06a4416f..a7212dbc 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -157,11 +157,28 @@ public function fetch(string $file, ?array $data = null): string $view = ob_get_clean(); } - preg_match('/[a-z-]+)\s*\/>/', $view, $matches); + preg_match( + '/[a-z-]+)\s*(?[a-z]+="[a-z-A-Z]+")*\s*\/>/', + $view, + $matches, + ); if ($matches) { $tag = $matches[0]; - $component = $this->fetch("components/{$matches['component']}"); + $component = $matches['component']; + $props = $matches['props'] ?? ''; + + preg_match( + '/^(?[a-z]+)="(?[a-zA-Z]+)"$/', + $props, + $matches, + ); + + $props = $matches + ? [$matches['name'] => $matches['value']] + : []; + + $component = $this->fetch("components/$component", $props); $view = str_replace($tag, $component, $view); } diff --git a/tests/ViewTest.php b/tests/ViewTest.php index a6abff5b..3f240281 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -320,6 +320,16 @@ public static function pagesDataProvider(): array another-class-component extended by my-class-component-that-extends-another-class-component html, ], + [ + 'page-with-component-with-one-prop', + <<<'html' + + +

Hello, James

+ + + html, + ], ]; } diff --git a/tests/views/components/greeting.php b/tests/views/components/greeting.php new file mode 100644 index 00000000..3a8fd49d --- /dev/null +++ b/tests/views/components/greeting.php @@ -0,0 +1 @@ +

Hello,

\ No newline at end of file diff --git a/tests/views/pages/page-with-component-with-one-prop.php b/tests/views/pages/page-with-component-with-one-prop.php new file mode 100644 index 00000000..72444355 --- /dev/null +++ b/tests/views/pages/page-with-component-with-one-prop.php @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From 38d4458e96a75b1b07d246b72f209c15c76dc223 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 24 Jun 2026 02:48:31 -0400 Subject: [PATCH 20/30] 5. views can repass props --- tests/ViewTest.php | 22 ++++++++++++++++--- .../page-with-component-with-one-prop.php | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 3f240281..bb5c47e5 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -235,11 +235,14 @@ public static function renderDataProvider(): array } /** @dataProvider pagesDataProvider */ - public function testItRendersComponent(string $page, string $expected): void - { + public function testItRendersComponent( + string $page, + string $expected, + array $props = [] + ): void { $view = new View(__DIR__ . '/views'); $view->preserveVars = false; - $actual = $view->fetch("pages/$page"); + $actual = $view->fetch("pages/$page", $props); self::assertSame( self::removeIndentation(self::removeLineEndings($expected)), @@ -329,7 +332,20 @@ public static function pagesDataProvider(): array html, + ['name' => 'James'], + ], + [ + 'page-with-component-with-one-prop', + <<<'html' + + +

Hello, Victoria

+ + + html, + ['name' => 'Victoria'], ], + ]; } diff --git a/tests/views/pages/page-with-component-with-one-prop.php b/tests/views/pages/page-with-component-with-one-prop.php index 72444355..9839e1a4 100644 --- a/tests/views/pages/page-with-component-with-one-prop.php +++ b/tests/views/pages/page-with-component-with-one-prop.php @@ -1,5 +1,5 @@ - + \ No newline at end of file From 5cdc2e85c0f5425d18d6ac7aae4992427faf36b6 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Wed, 24 Jun 2026 03:17:59 -0400 Subject: [PATCH 21/30] 6. renders self close components with many props --- flight/template/View.php | 22 ++++++++++++++----- tests/ViewTest.php | 12 +++++++++- .../components/greeting-with-two-props.php | 1 + .../page-with-component-with-two-props.php | 5 +++++ 4 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 tests/views/components/greeting-with-two-props.php create mode 100644 tests/views/pages/page-with-component-with-two-props.php diff --git a/flight/template/View.php b/flight/template/View.php index a7212dbc..d47eeff4 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -158,7 +158,7 @@ public function fetch(string $file, ?array $data = null): string } preg_match( - '/[a-z-]+)\s*(?[a-z]+="[a-z-A-Z]+")*\s*\/>/', + '/[a-z-]+)\s*(?([a-z]+="[a-zA-Z]+"\s*)*)?\s*\/>/', $view, $matches, ); @@ -168,15 +168,25 @@ public function fetch(string $file, ?array $data = null): string $component = $matches['component']; $props = $matches['props'] ?? ''; - preg_match( - '/^(?[a-z]+)="(?[a-zA-Z]+)"$/', + preg_match_all( + '/(?[a-z]+)="(?[a-zA-Z]+)"/', $props, $matches, ); - $props = $matches - ? [$matches['name'] => $matches['value']] - : []; + $matches = array_filter($matches); + + if ($matches) { + $props = []; + + foreach (array_keys($matches[0]) as $index) { + $name = $matches['name'][$index]; + $value = $matches['value'][$index]; + $props[$name] = $value; + } + } else { + $props = []; + } $component = $this->fetch("components/$component", $props); $view = str_replace($tag, $component, $view); diff --git a/tests/ViewTest.php b/tests/ViewTest.php index bb5c47e5..178c22c0 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -345,7 +345,17 @@ public static function pagesDataProvider(): array html, ['name' => 'Victoria'], ], - + [ + 'page-with-component-with-two-props', + <<<'html' + + +

Hello, Astronaut Victoria

+ + + html, + ['name' => 'Victoria', 'occupation' => 'Astronaut'], + ], ]; } diff --git a/tests/views/components/greeting-with-two-props.php b/tests/views/components/greeting-with-two-props.php new file mode 100644 index 00000000..24045d64 --- /dev/null +++ b/tests/views/components/greeting-with-two-props.php @@ -0,0 +1 @@ +

Hello,

\ No newline at end of file diff --git a/tests/views/pages/page-with-component-with-two-props.php b/tests/views/pages/page-with-component-with-two-props.php new file mode 100644 index 00000000..6cb90459 --- /dev/null +++ b/tests/views/pages/page-with-component-with-two-props.php @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From 8280d4ddaf0291f2273bc8b7d6b2cf4b419ba20c Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 25 Jun 2026 12:37:56 -0400 Subject: [PATCH 22/30] allow to change the prefix --- flight/template/View.php | 11 +++++++--- tests/ViewTest.php | 21 +++++++++++++++++++ ...page-with-component-with-custom-prefix.php | 1 + 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 tests/views/pages/page-with-component-with-custom-prefix.php diff --git a/flight/template/View.php b/flight/template/View.php index d47eeff4..1a3dc012 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -27,10 +27,15 @@ class View /** @var array View variables */ protected array $vars = []; + private string $componentPrefix; + /** @param string $path Path to templates directory */ - public function __construct(string $path = '.') - { + public function __construct( + string $path = ".", + string $componentPrefix = 'f' + ) { $this->path = $path; + $this->componentPrefix = $componentPrefix; } /** @@ -158,7 +163,7 @@ public function fetch(string $file, ?array $data = null): string } preg_match( - '/[a-z-]+)\s*(?([a-z]+="[a-zA-Z]+"\s*)*)?\s*\/>/', + "/<$this->componentPrefix-(?[a-z-]+)\s*(?([a-z]+=\"[a-zA-Z]+\"\s*)*)?\s*\/>/", $view, $matches, ); diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 178c22c0..d85b5f64 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -359,6 +359,27 @@ public static function pagesDataProvider(): array ]; } + /** @dataProvider prefixesDataProvider */ + public function testRenderComponentsWithDifferentPrefixes(string $prefix): void + { + $view = new View(__DIR__ . '/views', $prefix); + $view->preserveVars = false; + $actual = $view->fetch('pages/page-with-component-with-custom-prefix', compact('prefix')); + $expected = 'my-component'; + + self::assertSame( + self::removeIndentation(self::removeLineEndings($expected)), + self::removeIndentation(self::removeLineEndings($actual)), + ); + } + + public static function prefixesDataProvider(): array + { + return [ + ['x'], + ]; + } + private static function removeLineEndings(string $subject): string { return str_replace(["\r", "\n"], '', $subject); diff --git a/tests/views/pages/page-with-component-with-custom-prefix.php b/tests/views/pages/page-with-component-with-custom-prefix.php new file mode 100644 index 00000000..46b3dcee --- /dev/null +++ b/tests/views/pages/page-with-component-with-custom-prefix.php @@ -0,0 +1 @@ +<-my-component /> \ No newline at end of file From 5c4b9b985674ea0ac80fa02ad492d33e512da8b6 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 25 Jun 2026 13:48:14 -0400 Subject: [PATCH 23/30] allow to change the components path --- flight/template/View.php | 29 ++++++++++++++----- tests/ViewTest.php | 13 +++++++++ .../my-component-from-another-path.php | 1 + .../page-with-component-from-another-path.php | 1 + 4 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 tests/components/my-component-from-another-path.php create mode 100644 tests/views/pages/page-with-component-from-another-path.php diff --git a/flight/template/View.php b/flight/template/View.php index 1a3dc012..067d0086 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -28,14 +28,24 @@ class View protected array $vars = []; private string $componentPrefix; + private string $componentsPath; - /** @param string $path Path to templates directory */ + /** + * @param string $path Path to templates directory + * @param string $componentPrefix Prefix for component tags. + * For example, if the prefix is `f`, then a component tag would look like `` + * @param string $componentsPath Path to components directory. + * If is a relative path, it will be relative to the `$path` property. + * **We recomment that you always use absolute paths for explicitness**. + */ public function __construct( string $path = ".", - string $componentPrefix = 'f' + string $componentPrefix = 'f', + string $componentsPath = 'components' ) { $this->path = $path; $this->componentPrefix = $componentPrefix; + $this->componentsPath = $componentsPath; } /** @@ -68,7 +78,7 @@ public function set($key, $value = null) /** * Checks if a template variable is set - * @return bool If key exists + * @return bool If key exists and is not `null` */ public function has(string $key): bool { @@ -193,7 +203,7 @@ public function fetch(string $file, ?array $data = null): string $props = []; } - $component = $this->fetch("components/$component", $props); + $component = $this->fetch("$this->componentsPath/$component", $props); $view = str_replace($tag, $component, $view); } @@ -217,14 +227,17 @@ public function exists(string $file): bool */ public function getTemplate(string $file): string { - if (!empty($this->extension) && (substr($file, -1 * strlen($this->extension)) !== $this->extension)) { + $fileDoesNotHaveExtension = substr($file, -strlen($this->extension)) !== $this->extension; + + if ($fileDoesNotHaveExtension) { $file .= $this->extension; } - $is_windows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; + $isLinuxAbsolutePath = $file[0] === '/'; + $isWindowsAbsolutePath = PHP_OS === 'WINNT' && $file[1] === ':'; - if ((substr($file, 0, 1) === '/') || ($is_windows && substr($file, 1, 1) === ':')) { - return $file; + if ($isLinuxAbsolutePath || $isWindowsAbsolutePath) { + return $this::normalizePath($file); } return $this::normalizePath("$this->path/$file"); diff --git a/tests/ViewTest.php b/tests/ViewTest.php index d85b5f64..69f1f617 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -250,6 +250,19 @@ public function testItRendersComponent( ); } + public function testItRendersComponentFromAnotherPath(): void + { + $view = new View(__DIR__ . '/views', 'f', __DIR__ . '/components'); + $view->preserveVars = false; + $actual = $view->fetch('pages/page-with-component-from-another-path'); + $expected = 'my-component-from-another-path'; + + self::assertSame( + self::removeIndentation(self::removeLineEndings($expected)), + self::removeIndentation(self::removeLineEndings($actual)), + ); + } + public static function pagesDataProvider(): array { return [ diff --git a/tests/components/my-component-from-another-path.php b/tests/components/my-component-from-another-path.php new file mode 100644 index 00000000..b234a7d2 --- /dev/null +++ b/tests/components/my-component-from-another-path.php @@ -0,0 +1 @@ +my-component-from-another-path diff --git a/tests/views/pages/page-with-component-from-another-path.php b/tests/views/pages/page-with-component-from-another-path.php new file mode 100644 index 00000000..7ab559fa --- /dev/null +++ b/tests/views/pages/page-with-component-from-another-path.php @@ -0,0 +1 @@ + \ No newline at end of file From 5076b266b3f1da4b5d10832bf77086485146c2b3 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 25 Jun 2026 13:54:40 -0400 Subject: [PATCH 24/30] allow custom indentations in tests (> 1 space) --- tests/ViewTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 69f1f617..e855fff4 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -400,6 +400,6 @@ private static function removeLineEndings(string $subject): string private static function removeIndentation(string $subject): string { - return str_replace(' ', '', $subject); + return preg_replace('/\s{2,}/', '', $subject); } } From 52ec6b88031b1fc0dc97d4d415b17a1237402035 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 25 Jun 2026 14:43:25 -0400 Subject: [PATCH 25/30] allow to render different components --- flight/template/View.php | 26 ++++++++++--------- tests/ViewTest.php | 8 ++++++ .../page-with-three-different-components.php | 3 +++ 3 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 tests/views/pages/page-with-three-different-components.php diff --git a/flight/template/View.php b/flight/template/View.php index 067d0086..e3f8993e 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -172,31 +172,33 @@ public function fetch(string $file, ?array $data = null): string $view = ob_get_clean(); } - preg_match( + preg_match_all( "/<$this->componentPrefix-(?[a-z-]+)\s*(?([a-z]+=\"[a-zA-Z]+\"\s*)*)?\s*\/>/", $view, - $matches, + $tagsMatches, ); - if ($matches) { - $tag = $matches[0]; - $component = $matches['component']; - $props = $matches['props'] ?? ''; + $tagsMatches = array_filter($tagsMatches); + + foreach ($tagsMatches[0] ?? [] as $tagIndex => $match) { + $tag = $match; + $component = $tagsMatches['component'][$tagIndex]; + $props = $tagsMatches['props'][$tagIndex] ?? ''; preg_match_all( '/(?[a-z]+)="(?[a-zA-Z]+)"/', $props, - $matches, + $propsMatches, ); - $matches = array_filter($matches); + $propsMatches = array_filter($propsMatches); - if ($matches) { + if ($propsMatches) { $props = []; - foreach (array_keys($matches[0]) as $index) { - $name = $matches['name'][$index]; - $value = $matches['value'][$index]; + foreach (array_keys($propsMatches[0]) as $propIndex) { + $name = $propsMatches['name'][$propIndex]; + $value = $propsMatches['value'][$propIndex]; $props[$name] = $value; } } else { diff --git a/tests/ViewTest.php b/tests/ViewTest.php index e855fff4..34ee4d1f 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -369,6 +369,14 @@ public static function pagesDataProvider(): array html, ['name' => 'Victoria', 'occupation' => 'Astronaut'], ], + [ + 'page-with-three-different-components', + <<<'html' + my-component + my-functional-component + my-class-component + html, + ], ]; } diff --git a/tests/views/pages/page-with-three-different-components.php b/tests/views/pages/page-with-three-different-components.php new file mode 100644 index 00000000..a17fe68f --- /dev/null +++ b/tests/views/pages/page-with-three-different-components.php @@ -0,0 +1,3 @@ + + + \ No newline at end of file From 8c88b9d94a3eed9ceb8c5ff135a64a9f9858ae60 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 25 Jun 2026 14:56:39 -0400 Subject: [PATCH 26/30] allow props which values contains spaces and numbers --- flight/template/View.php | 4 ++-- tests/ViewTest.php | 7 +++++++ ...t-with-prop-which-value-contains-spaces-and-numbers.php | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 tests/views/pages/page-with-component-with-prop-which-value-contains-spaces-and-numbers.php diff --git a/flight/template/View.php b/flight/template/View.php index e3f8993e..10a55b26 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -173,7 +173,7 @@ public function fetch(string $file, ?array $data = null): string } preg_match_all( - "/<$this->componentPrefix-(?[a-z-]+)\s*(?([a-z]+=\"[a-zA-Z]+\"\s*)*)?\s*\/>/", + "/<$this->componentPrefix-(?[a-z-]+)\s*(?([a-z]+=\"[^\"]+\"\s*)*)?\s*\/>/", $view, $tagsMatches, ); @@ -186,7 +186,7 @@ public function fetch(string $file, ?array $data = null): string $props = $tagsMatches['props'][$tagIndex] ?? ''; preg_match_all( - '/(?[a-z]+)="(?[a-zA-Z]+)"/', + '/(?[a-z]+)="(?[^"]+)"/', $props, $propsMatches, ); diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 34ee4d1f..39c4a6fc 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -377,6 +377,13 @@ public static function pagesDataProvider(): array my-class-component html, ], + [ + 'page-with-component-with-prop-which-value-contains-spaces-and-numbers', + <<<'html' +

Hello, Constantine 1st the Great

+ html, + ['name' => 'Constantine 1st the Great'], + ], ]; } diff --git a/tests/views/pages/page-with-component-with-prop-which-value-contains-spaces-and-numbers.php b/tests/views/pages/page-with-component-with-prop-which-value-contains-spaces-and-numbers.php new file mode 100644 index 00000000..622b8339 --- /dev/null +++ b/tests/views/pages/page-with-component-with-prop-which-value-contains-spaces-and-numbers.php @@ -0,0 +1 @@ + \ No newline at end of file From 7acf6c00b93dd37ce34d6a2bc5018410ca33fb17 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 25 Jun 2026 16:37:52 -0400 Subject: [PATCH 27/30] fix styles and scripts duplication --- flight/template/View.php | 19 +- tests/ViewTest.php | 229 ++++++++++-------- ...mponents-with-one-style-and-script-tag.php | 5 + 3 files changed, 145 insertions(+), 108 deletions(-) create mode 100644 tests/views/pages/page-two-same-components-with-one-style-and-script-tag.php diff --git a/flight/template/View.php b/flight/template/View.php index 10a55b26..1db48ef6 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -149,20 +149,27 @@ public function fetch(string $file, ?array $data = null): string $css = $component->css(); $js = $component->js(); - if ($css) { + static $styles = []; + static $scripts = []; + + if ($css && !array_key_exists($template, $styles)) { $view .= << $css html; + + $styles[$template] = true; } - if ($js) { + if ($js && !array_key_exists($template, $scripts)) { $view .= << $js html; + + $scripts[$template] = true; } ob_end_clean(); @@ -206,7 +213,13 @@ public function fetch(string $file, ?array $data = null): string } $component = $this->fetch("$this->componentsPath/$component", $props); - $view = str_replace($tag, $component, $view); + $tagPosition = strpos($view, $tag); + + if ($tagPosition === false) { + continue; + } + + $view = substr_replace($view, $component, $tagPosition, strlen($tag)); } return $view; diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 39c4a6fc..520140a6 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -266,123 +266,142 @@ public function testItRendersComponentFromAnotherPath(): void public static function pagesDataProvider(): array { return [ + // [ + // 'page-with-component-with-old-syntax', + // <<<'html' + // my-component + // html, + // ], + // [ + // 'page-with-component-with-new-syntax', + // <<<'html' + // my-component + // html, + // ], + // [ + // 'page-with-component-with-subcomponent', + // <<<'html' + //
+ // my-component-with-subcomponent + // subcomponent + //
+ // html, + // ], + // [ + // 'page-with-multiple-components', + // <<<'html' + //
    + //
  • my-component
  • + //
  • my-component
  • + //
+ // html, + // ], + // [ + // 'page-with-functional-component', + // <<<'html' + // my-functional-component + // html, + // ], + // [ + // 'page-with-class-component', + // <<<'html' + // my-class-component + // html, + // ], + // [ + // 'page-with-class-component-with-styles', + // <<<'html' + // + // my-class-component-with-styles + // + + // + // html, + // ], + // [ + // 'page-with-class-component-with-scripts', + // <<<'html' + // my-class-component-with-scripts + + // + // html, + // ], + // [ + // 'page-with-class-component-that-extends-another-class-component', + // <<<'html' + // another-class-component extended by my-class-component-that-extends-another-class-component + // html, + // ], + // [ + // 'page-with-component-with-one-prop', + // <<<'html' + // + // + //

Hello, James

+ // + // + // html, + // ['name' => 'James'], + // ], + // [ + // 'page-with-component-with-one-prop', + // <<<'html' + // + // + //

Hello, Victoria

+ // + // + // html, + // ['name' => 'Victoria'], + // ], + // [ + // 'page-with-component-with-two-props', + // <<<'html' + // + // + //

Hello, Astronaut Victoria

+ // + // + // html, + // ['name' => 'Victoria', 'occupation' => 'Astronaut'], + // ], + // [ + // 'page-with-three-different-components', + // <<<'html' + // my-component + // my-functional-component + // my-class-component + // html, + // ], + // [ + // 'page-with-component-with-prop-which-value-contains-spaces-and-numbers', + // <<<'html' + //

Hello, Constantine 1st the Great

+ // html, + // ['name' => 'Constantine 1st the Great'], + // ], [ - 'page-with-component-with-old-syntax', - <<<'html' - my-component - html, - ], - [ - 'page-with-component-with-new-syntax', - <<<'html' - my-component - html, - ], - [ - 'page-with-component-with-subcomponent', - <<<'html' -
- my-component-with-subcomponent - subcomponent -
- html, - ], - [ - 'page-with-multiple-components', - <<<'html' -
    -
  • my-component
  • -
  • my-component
  • -
- html, - ], - [ - 'page-with-functional-component', - <<<'html' - my-functional-component - html, - ], - [ - 'page-with-class-component', - <<<'html' - my-class-component - html, - ], - [ - 'page-with-class-component-with-styles', + 'page-two-same-components-with-one-style-and-script-tag', <<<'html' my-class-component-with-styles - - html, - ], - [ - 'page-with-class-component-with-scripts', - <<<'html' + + my-class-component-with-styles + my-class-component-with-scripts - - html, - ], - [ - 'page-with-class-component-that-extends-another-class-component', - <<<'html' - another-class-component extended by my-class-component-that-extends-another-class-component - html, - ], - [ - 'page-with-component-with-one-prop', - <<<'html' - - -

Hello, James

- - - html, - ['name' => 'James'], - ], - [ - 'page-with-component-with-one-prop', - <<<'html' - - -

Hello, Victoria

- - - html, - ['name' => 'Victoria'], - ], - [ - 'page-with-component-with-two-props', - <<<'html' - - -

Hello, Astronaut Victoria

- - - html, - ['name' => 'Victoria', 'occupation' => 'Astronaut'], - ], - [ - 'page-with-three-different-components', - <<<'html' - my-component - my-functional-component - my-class-component - html, - ], - [ - 'page-with-component-with-prop-which-value-contains-spaces-and-numbers', - <<<'html' -

Hello, Constantine 1st the Great

- html, - ['name' => 'Constantine 1st the Great'], + my-class-component-with-scripts + html, ], ]; } diff --git a/tests/views/pages/page-two-same-components-with-one-style-and-script-tag.php b/tests/views/pages/page-two-same-components-with-one-style-and-script-tag.php new file mode 100644 index 00000000..8dfdf979 --- /dev/null +++ b/tests/views/pages/page-two-same-components-with-one-style-and-script-tag.php @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From 8c277dadd6f05f36f2dff719695e911e5f4f647a Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 25 Jun 2026 16:48:54 -0400 Subject: [PATCH 28/30] Fix component style/script dedupe leakage Use per-render instance state for component style/script dedupe in View::fetch so assets are deduped within a render without leaking across tests or subsequent renders. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- flight/template/View.php | 173 +++++++++++++++------------- tests/ViewTest.php | 236 +++++++++++++++++++-------------------- 2 files changed, 211 insertions(+), 198 deletions(-) diff --git a/flight/template/View.php b/flight/template/View.php index 1db48ef6..8983db58 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -29,6 +29,11 @@ class View private string $componentPrefix; private string $componentsPath; + private int $fetchDepth = 0; + /** @var array */ + private array $styles = []; + /** @var array */ + private array $scripts = []; /** * @param string $path Path to templates directory @@ -119,110 +124,118 @@ public function render(string $file, ?array $templateData = null): void */ public function fetch(string $file, ?array $data = null): string { - $template = $this->getTemplate($file); - - if (!$this->exists($file)) { - throw new Exception("Template file not found: $template."); + if ($this->fetchDepth === 0) { + $this->styles = []; + $this->scripts = []; } - extract($this->vars); + $this->fetchDepth++; - if (is_array($data)) { - extract($data); + try { + $template = $this->getTemplate($file); - if ($this->preserveVars) { - $this->vars += $data; + if (!$this->exists($file)) { + throw new Exception("Template file not found: $template."); } - } - - ob_start(); - $component = include $template; - - switch (true) { - case is_callable($component): - $view = $component(); - ob_end_clean(); - - break; - case $component instanceof Component: - $view = $component->html(); - $css = $component->css(); - $js = $component->js(); - static $styles = []; - static $scripts = []; + extract($this->vars); - if ($css && !array_key_exists($template, $styles)) { - $view .= << - $css - - html; + if (is_array($data)) { + extract($data); - $styles[$template] = true; + if ($this->preserveVars) { + $this->vars += $data; } + } - if ($js && !array_key_exists($template, $scripts)) { - $view .= << - $js - - html; - - $scripts[$template] = true; - } + ob_start(); + $component = include $template; + + switch (true) { + case is_callable($component): + $view = $component(); + ob_end_clean(); + + break; + case $component instanceof Component: + $view = $component->html(); + $css = $component->css(); + $js = $component->js(); + + if ($css && !array_key_exists($template, $this->styles)) { + $view .= << + $css + + html; + + $this->styles[$template] = true; + } + + if ($js && !array_key_exists($template, $this->scripts)) { + $view .= << + $js + + html; + + $this->scripts[$template] = true; + } + + ob_end_clean(); + + break; + default: + $view = ob_get_clean(); + } - ob_end_clean(); + preg_match_all( + "/<$this->componentPrefix-(?[a-z-]+)\s*(?([a-z]+=\"[^\"]+\"\s*)*)?\s*\/>/", + $view, + $tagsMatches, + ); - break; - default: - $view = ob_get_clean(); - } + $tagsMatches = array_filter($tagsMatches); - preg_match_all( - "/<$this->componentPrefix-(?[a-z-]+)\s*(?([a-z]+=\"[^\"]+\"\s*)*)?\s*\/>/", - $view, - $tagsMatches, - ); + foreach ($tagsMatches[0] ?? [] as $tagIndex => $match) { + $tag = $match; + $component = $tagsMatches['component'][$tagIndex]; + $props = $tagsMatches['props'][$tagIndex] ?? ''; - $tagsMatches = array_filter($tagsMatches); + preg_match_all( + '/(?[a-z]+)="(?[^"]+)"/', + $props, + $propsMatches, + ); - foreach ($tagsMatches[0] ?? [] as $tagIndex => $match) { - $tag = $match; - $component = $tagsMatches['component'][$tagIndex]; - $props = $tagsMatches['props'][$tagIndex] ?? ''; + $propsMatches = array_filter($propsMatches); - preg_match_all( - '/(?[a-z]+)="(?[^"]+)"/', - $props, - $propsMatches, - ); + if ($propsMatches) { + $props = []; - $propsMatches = array_filter($propsMatches); + foreach (array_keys($propsMatches[0]) as $propIndex) { + $name = $propsMatches['name'][$propIndex]; + $value = $propsMatches['value'][$propIndex]; + $props[$name] = $value; + } + } else { + $props = []; + } - if ($propsMatches) { - $props = []; + $component = $this->fetch("$this->componentsPath/$component", $props); + $tagPosition = strpos($view, $tag); - foreach (array_keys($propsMatches[0]) as $propIndex) { - $name = $propsMatches['name'][$propIndex]; - $value = $propsMatches['value'][$propIndex]; - $props[$name] = $value; + if ($tagPosition === false) { + continue; } - } else { - $props = []; - } - $component = $this->fetch("$this->componentsPath/$component", $props); - $tagPosition = strpos($view, $tag); - - if ($tagPosition === false) { - continue; + $view = substr_replace($view, $component, $tagPosition, strlen($tag)); } - $view = substr_replace($view, $component, $tagPosition, strlen($tag)); + return $view; + } finally { + $this->fetchDepth--; } - - return $view; } /** diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 520140a6..ce37cd6a 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -266,124 +266,124 @@ public function testItRendersComponentFromAnotherPath(): void public static function pagesDataProvider(): array { return [ - // [ - // 'page-with-component-with-old-syntax', - // <<<'html' - // my-component - // html, - // ], - // [ - // 'page-with-component-with-new-syntax', - // <<<'html' - // my-component - // html, - // ], - // [ - // 'page-with-component-with-subcomponent', - // <<<'html' - //
- // my-component-with-subcomponent - // subcomponent - //
- // html, - // ], - // [ - // 'page-with-multiple-components', - // <<<'html' - //
    - //
  • my-component
  • - //
  • my-component
  • - //
- // html, - // ], - // [ - // 'page-with-functional-component', - // <<<'html' - // my-functional-component - // html, - // ], - // [ - // 'page-with-class-component', - // <<<'html' - // my-class-component - // html, - // ], - // [ - // 'page-with-class-component-with-styles', - // <<<'html' - // - // my-class-component-with-styles - // - - // - // html, - // ], - // [ - // 'page-with-class-component-with-scripts', - // <<<'html' - // my-class-component-with-scripts - - // - // html, - // ], - // [ - // 'page-with-class-component-that-extends-another-class-component', - // <<<'html' - // another-class-component extended by my-class-component-that-extends-another-class-component - // html, - // ], - // [ - // 'page-with-component-with-one-prop', - // <<<'html' - // - // - //

Hello, James

- // - // - // html, - // ['name' => 'James'], - // ], - // [ - // 'page-with-component-with-one-prop', - // <<<'html' - // - // - //

Hello, Victoria

- // - // - // html, - // ['name' => 'Victoria'], - // ], - // [ - // 'page-with-component-with-two-props', - // <<<'html' - // - // - //

Hello, Astronaut Victoria

- // - // - // html, - // ['name' => 'Victoria', 'occupation' => 'Astronaut'], - // ], - // [ - // 'page-with-three-different-components', - // <<<'html' - // my-component - // my-functional-component - // my-class-component - // html, - // ], - // [ - // 'page-with-component-with-prop-which-value-contains-spaces-and-numbers', - // <<<'html' - //

Hello, Constantine 1st the Great

- // html, - // ['name' => 'Constantine 1st the Great'], - // ], + [ + 'page-with-component-with-old-syntax', + <<<'html' + my-component + html, + ], + [ + 'page-with-component-with-new-syntax', + <<<'html' + my-component + html, + ], + [ + 'page-with-component-with-subcomponent', + <<<'html' +
+ my-component-with-subcomponent + subcomponent +
+ html, + ], + [ + 'page-with-multiple-components', + <<<'html' +
    +
  • my-component
  • +
  • my-component
  • +
+ html, + ], + [ + 'page-with-functional-component', + <<<'html' + my-functional-component + html, + ], + [ + 'page-with-class-component', + <<<'html' + my-class-component + html, + ], + [ + 'page-with-class-component-with-styles', + <<<'html' + + my-class-component-with-styles + + + + html, + ], + [ + 'page-with-class-component-with-scripts', + <<<'html' + my-class-component-with-scripts + + + html, + ], + [ + 'page-with-class-component-that-extends-another-class-component', + <<<'html' + another-class-component extended by my-class-component-that-extends-another-class-component + html, + ], + [ + 'page-with-component-with-one-prop', + <<<'html' + + +

Hello, James

+ + + html, + ['name' => 'James'], + ], + [ + 'page-with-component-with-one-prop', + <<<'html' + + +

Hello, Victoria

+ + + html, + ['name' => 'Victoria'], + ], + [ + 'page-with-component-with-two-props', + <<<'html' + + +

Hello, Astronaut Victoria

+ + + html, + ['name' => 'Victoria', 'occupation' => 'Astronaut'], + ], + [ + 'page-with-three-different-components', + <<<'html' + my-component + my-functional-component + my-class-component + html, + ], + [ + 'page-with-component-with-prop-which-value-contains-spaces-and-numbers', + <<<'html' +

Hello, Constantine 1st the Great

+ html, + ['name' => 'Constantine 1st the Great'], + ], [ 'page-two-same-components-with-one-style-and-script-tag', <<<'html' From dd238319dad41e63de9ae2dedf2cf334bfd6f5c1 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 25 Jun 2026 17:05:46 -0400 Subject: [PATCH 29/30] Add component prop-passing coverage Extend View component rendering tests to cover functional and class components receiving props via variadic and individual parameters, and update View callable invocation to map template data into callable arguments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- flight/template/View.php | 40 ++++++++++++++++++- tests/ViewTest.php | 21 ++++++++++ .../my-class-component-with-props.php | 22 ++++++++++ ...tional-component-with-individual-props.php | 6 +++ .../my-functional-component-with-props.php | 9 +++++ .../page-with-class-component-with-props.php | 1 + ...tional-component-with-individual-props.php | 1 + ...e-with-functional-component-with-props.php | 1 + 8 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tests/views/components/my-class-component-with-props.php create mode 100644 tests/views/components/my-functional-component-with-individual-props.php create mode 100644 tests/views/components/my-functional-component-with-props.php create mode 100644 tests/views/pages/page-with-class-component-with-props.php create mode 100644 tests/views/pages/page-with-functional-component-with-individual-props.php create mode 100644 tests/views/pages/page-with-functional-component-with-props.php diff --git a/flight/template/View.php b/flight/template/View.php index 8983db58..453f5337 100644 --- a/flight/template/View.php +++ b/flight/template/View.php @@ -4,7 +4,9 @@ namespace flight\template; +use Closure; use Exception; +use ReflectionFunction; /** * The View class represents output to be displayed. It provides @@ -153,7 +155,8 @@ public function fetch(string $file, ?array $data = null): string switch (true) { case is_callable($component): - $view = $component(); + $arguments = $this->getCallableArguments($component, $data); + $view = $component(...$arguments); ob_end_clean(); break; @@ -290,4 +293,39 @@ protected static function normalizePath( ): string { return str_replace(['\\', '/'], $separator, $path); } + + /** + * @param callable $component + * @param ?array $data + * @return array + */ + private function getCallableArguments(callable $component, ?array $data): array + { + if (!is_array($data) || !$data) { + return []; + } + + $arguments = []; + $remainingData = $data; + $reflection = new ReflectionFunction(Closure::fromCallable($component)); + + foreach ($reflection->getParameters() as $parameter) { + if ($parameter->isVariadic()) { + foreach ($remainingData as $value) { + $arguments[] = $value; + } + + break; + } + + $name = $parameter->getName(); + + if (array_key_exists($name, $remainingData)) { + $arguments[] = $remainingData[$name]; + unset($remainingData[$name]); + } + } + + return $arguments; + } } diff --git a/tests/ViewTest.php b/tests/ViewTest.php index ce37cd6a..78c5d2d5 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -308,6 +308,27 @@ public static function pagesDataProvider(): array my-class-component html, ], + [ + 'page-with-functional-component-with-props', + <<<'html' + functional-component-with-props: Astronaut Victoria + html, + ['name' => 'Victoria', 'occupation' => 'Astronaut'], + ], + [ + 'page-with-functional-component-with-individual-props', + <<<'html' + functional-component-with-individual-props: Astronaut Victoria + html, + ['name' => 'Victoria', 'occupation' => 'Astronaut'], + ], + [ + 'page-with-class-component-with-props', + <<<'html' + class-component-with-props: Astronaut Victoria + html, + ['name' => 'Victoria', 'occupation' => 'Astronaut'], + ], [ 'page-with-class-component-with-styles', <<<'html' diff --git a/tests/views/components/my-class-component-with-props.php b/tests/views/components/my-class-component-with-props.php new file mode 100644 index 00000000..66abcad6 --- /dev/null +++ b/tests/views/components/my-class-component-with-props.php @@ -0,0 +1,22 @@ +name = $name; + $this->occupation = $occupation; + } + + public function html(): string + { + return "class-component-with-props: $this->occupation $this->name"; + } +}; diff --git a/tests/views/components/my-functional-component-with-individual-props.php b/tests/views/components/my-functional-component-with-individual-props.php new file mode 100644 index 00000000..9f3bdb92 --- /dev/null +++ b/tests/views/components/my-functional-component-with-individual-props.php @@ -0,0 +1,6 @@ + + "functional-component-with-individual-props: $occupation $name"; diff --git a/tests/views/components/my-functional-component-with-props.php b/tests/views/components/my-functional-component-with-props.php new file mode 100644 index 00000000..ef8856e7 --- /dev/null +++ b/tests/views/components/my-functional-component-with-props.php @@ -0,0 +1,9 @@ + sprintf( + 'functional-component-with-props: %s %s', + $props[1] ?? '', + $props[0] ?? '' +); diff --git a/tests/views/pages/page-with-class-component-with-props.php b/tests/views/pages/page-with-class-component-with-props.php new file mode 100644 index 00000000..a2bd6d90 --- /dev/null +++ b/tests/views/pages/page-with-class-component-with-props.php @@ -0,0 +1 @@ + diff --git a/tests/views/pages/page-with-functional-component-with-individual-props.php b/tests/views/pages/page-with-functional-component-with-individual-props.php new file mode 100644 index 00000000..f77480af --- /dev/null +++ b/tests/views/pages/page-with-functional-component-with-individual-props.php @@ -0,0 +1 @@ + diff --git a/tests/views/pages/page-with-functional-component-with-props.php b/tests/views/pages/page-with-functional-component-with-props.php new file mode 100644 index 00000000..dddef9f5 --- /dev/null +++ b/tests/views/pages/page-with-functional-component-with-props.php @@ -0,0 +1 @@ + From 48035d6a925e73d486927b48e594e9eba27e03e0 Mon Sep 17 00:00:00 2001 From: fadrian06 Date: Thu, 25 Jun 2026 17:10:58 -0400 Subject: [PATCH 30/30] Support custom style/script tags in class components Allow class components to return full - html; + $view .= $this->renderComponentStyle($css); $this->styles[$template] = true; } if ($js && !array_key_exists($template, $this->scripts)) { - $view .= << - $js - - html; + $view .= $this->renderComponentScript($js); $this->scripts[$template] = true; } @@ -328,4 +320,30 @@ private function getCallableArguments(callable $component, ?array $data): array return $arguments; } + + private function renderComponentStyle(string $css): string + { + if (preg_match('/^\s*]*>.*<\/style>\s*$/is', $css) === 1) { + return $css; + } + + return << + $css + + html; + } + + private function renderComponentScript(string $js): string + { + if (preg_match('/^\s*]*>.*<\/script>\s*$/is', $js) === 1) { + return $js; + } + + return << + $js + + html; + } } diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 78c5d2d5..1357fb70 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -343,6 +343,19 @@ class-component-with-props: Astronaut Victoria html, ], + [ + 'page-with-class-component-with-custom-style-tag', + <<<'html' + + my-class-component-with-custom-style-tag + + + html, + ], [ 'page-with-class-component-with-scripts', <<<'html' @@ -351,6 +364,15 @@ class-component-with-props: Astronaut Victoria html, ], + [ + 'page-with-class-component-with-custom-script-tag', + <<<'html' + my-class-component-with-custom-script-tag + + html, + ], [ 'page-with-class-component-that-extends-another-class-component', <<<'html' diff --git a/tests/views/components/my-class-component-with-custom-script-tag.php b/tests/views/components/my-class-component-with-custom-script-tag.php new file mode 100644 index 00000000..87ee8b12 --- /dev/null +++ b/tests/views/components/my-class-component-with-custom-script-tag.php @@ -0,0 +1,22 @@ + + console.log('my-class-component-with-custom-script-tag') + + js; + } +}; diff --git a/tests/views/components/my-class-component-with-custom-style-tag.php b/tests/views/components/my-class-component-with-custom-style-tag.php new file mode 100644 index 00000000..f6c65b88 --- /dev/null +++ b/tests/views/components/my-class-component-with-custom-style-tag.php @@ -0,0 +1,28 @@ + + my-class-component-with-custom-style-tag + + html; + } + + public function css(): string + { + return <<<'css' + + css; + } +}; diff --git a/tests/views/pages/page-with-class-component-with-custom-script-tag.php b/tests/views/pages/page-with-class-component-with-custom-script-tag.php new file mode 100644 index 00000000..bec475ec --- /dev/null +++ b/tests/views/pages/page-with-class-component-with-custom-script-tag.php @@ -0,0 +1 @@ + diff --git a/tests/views/pages/page-with-class-component-with-custom-style-tag.php b/tests/views/pages/page-with-class-component-with-custom-style-tag.php new file mode 100644 index 00000000..5f210436 --- /dev/null +++ b/tests/views/pages/page-with-class-component-with-custom-style-tag.php @@ -0,0 +1 @@ +