From 2f97b38e6689fae023ceca4ec74471072f29d404 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 21:00:51 +0000 Subject: [PATCH 01/30] Removed properties attribute --- src/Component.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Component.php b/src/Component.php index ed482b5..99a2226 100644 --- a/src/Component.php +++ b/src/Component.php @@ -18,16 +18,6 @@ abstract class Component */ protected $trim = true; - /** - * The supported attributes on the component. - * - * All keys in this list will have a setter method available - * named set followed by the property name with first letter uppercase. - * - * @var array - */ - protected $properties = ['children' => null]; - final public function __construct() { } From 0ae0864f0468e624f145c00d9e23ac1cfbad335e Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 21:02:28 +0000 Subject: [PATCH 02/30] Removed methods no longer used in v2 --- src/Component.php | 119 ---------------------------------------------- 1 file changed, 119 deletions(-) diff --git a/src/Component.php b/src/Component.php index 99a2226..6cf49f0 100644 --- a/src/Component.php +++ b/src/Component.php @@ -42,125 +42,6 @@ public function getProperties() return $this->properties; } - /** - * Get component property value, or null if non existent. - * - * @return mixed|null - */ - public function getProperty(string $property) - { - return $this->getProperties()[$property] ?? null; - } - - /** - * Check if the component supports a property by name. - * - * @param string $property - * - * @return boolean - */ - public function hasProperty(string $property) - { - return array_key_exists($property, $this->properties); - } - - /** - * Magic method for property setters support. - * - * @param string $method - * @param array $arguments - * - * @return $this - */ - public function __call(string $method, $arguments) - { - if (!preg_match('/^set([A-Z].*)$/', $method, $matches)) { - throw new \Exception("Call to undefined method \"$method\""); - } - - $property = lcFirst($matches[1]); - - if (!array_key_exists($property, $this->properties)) { - $className = get_class($this); - throw new \Exception("Component property \"$property\" is not defined on \"$className\""); - } - - $this->properties[$property] = $arguments[0] ?? null; - - return $this; - } - - /** - * Get *REAL* HTMLNode children. - * - * *REAL*, meaning that child components would produce a root HTMLNode, - * making changes to the child data appear as not working in some cases. - * - * @return HtmlNode[] - */ - public function getHTMLNodeChildren() - { - $HTMLNodes = []; - $children = $this->properties['children'] ?? []; - foreach ($children as $child) { - if ($child instanceof RootNode) { - foreach ($child->getChildren() as $child) { - if ($child instanceof HtmlNode) { - $HTMLNodes[] = $child; - } - } - } elseif ($child instanceof HtmlNode) { - $HTMLNodes[] = $child; - } - } - - return $HTMLNodes; - } - - /** - * Renders each child and returns the concatenated strings. - * - * @return string - */ - public function renderChildren(): string - { - return implode( - '', - array_map( - function ($child) { - return strval($child); - }, - $this->properties['children'] ?? [] - ) - ); - } - - /** - * Renders the component and returns the resulting string. - * - * @return string|Stringable - */ - public function render() - { - ob_start(); - //TODO: support the _render also being able to return value, but throw warning if neither ob_get_contents or the return value are empty! - $return = $this->_render(); - $result = ob_get_contents(); - ob_end_clean(); - if ($return !== null) { - if ($result !== "" && $result !== false) { - $className = str_replace("\0", "", get_class($this)); - throw new \ErrorException("component $className::_render produced content to the output buffer AND returned a non null value", 0, E_WARNING); - } else { - $result = $return; - } - } - if ($this->trim) { - $result = trim($result); - } - return $result; - } - /** * Render component content. * @return \Stringable|string|void From d7d889a546669bfa18bc81d82152e15d8754f008 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 21:07:00 +0000 Subject: [PATCH 03/30] Changed abstract render method naming and visibility --- src/Component.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Component.php b/src/Component.php index 6cf49f0..e273cb2 100644 --- a/src/Component.php +++ b/src/Component.php @@ -44,9 +44,9 @@ public function getProperties() /** * Render component content. - * @return \Stringable|string|void + * @return Stringable|string|void */ - abstract protected function _render(); + abstract public function render(); public function __toString(): string { From b68f8c7b685eb517c0b288c777c70429a9089510 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 21:08:05 +0000 Subject: [PATCH 04/30] Changed visibility for property --- src/Component.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Component.php b/src/Component.php index e273cb2..51ba3cc 100644 --- a/src/Component.php +++ b/src/Component.php @@ -16,7 +16,7 @@ abstract class Component * * @var boolean */ - protected $trim = true; + public $trim = true; final public function __construct() { From 7ca53c54678012ae2c2a2cc09b41ad383d3b867a Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 21:09:12 +0000 Subject: [PATCH 05/30] Changed implementation of __toString --- src/Component.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Component.php b/src/Component.php index 51ba3cc..9e21b3d 100644 --- a/src/Component.php +++ b/src/Component.php @@ -50,6 +50,12 @@ abstract public function render(); public function __toString(): string { - return $this->render(); + $rendered = View::renderComponent($this); + + if ($rendered instanceof Stringable) { + return $rendered->__toString(); + } + + return $rendered; } } From bc3a6c29d8bfa63a5a1b02bd299fc57cbafacd7d Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 21:11:47 +0000 Subject: [PATCH 06/30] Changed minimum required PHP version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f6a2e5f..975e667 100755 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ } ], "require": { - "php": ">=7.2", + "php": ">=7.4", "paquettg/php-html-parser": "~3.1.1" }, "require-dev": { From f201507181f21c52f5a2bb4c3c29316ca482d73a Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 23:11:39 +0000 Subject: [PATCH 07/30] Changed view html component attribute mapping to instance attribute values --- src/View.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/View.php b/src/View.php index 02b0ca7..f37627e 100644 --- a/src/View.php +++ b/src/View.php @@ -123,16 +123,7 @@ protected function processNode($node) $class = new $className(); foreach ($node->getAttributes() as $attributeKey => $attributeValue) { - $setter = "set" . ucfirst($attributeKey); - $class->$setter($attributeValue); - } - - if (count($node->getChildren()) > 0) { - if ($class->hasProperty('children') || method_exists($class, 'setChildren')) { - $class->setChildren($node->getChildren()); - } else { - trigger_error("children are passed to $className but are not supported by the component", E_USER_WARNING);//TODO: verbose level - } + $class->{$attributeKey} = $attributeValue; } $html = strval($class); From 07723c52f498c5894c4f9097870f2923258ef8e0 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 23:12:58 +0000 Subject: [PATCH 08/30] Changed one liner to be over multiple lines, after max line length phpcs rule --- src/View.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/View.php b/src/View.php index f37627e..24d69ad 100644 --- a/src/View.php +++ b/src/View.php @@ -137,7 +137,12 @@ protected function processNode($node) $this->resolvedView = $resolvedView; } } catch (\Throwable $throwable) { - $exception = new ProcessNodeException($throwable, realpath($this->resolvedView), $className ?? $node->tag->name(), is_null($node ?? null) ? null : $node->getLocation()); + $exception = new ProcessNodeException( + $throwable, + realpath($this->resolvedView), + $className ?? $node->tag->name(), + is_null($node ?? null) ? null : $node->getLocation() + ); throw $exception; } From 0e14f812d4b0098771f9e70b3d2b58b595bd3543 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 23:13:34 +0000 Subject: [PATCH 09/30] Added new static render method for components --- src/View.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/View.php b/src/View.php index 24d69ad..5855b94 100644 --- a/src/View.php +++ b/src/View.php @@ -7,6 +7,8 @@ use Genius257\View\Dom\Node\HtmlNode; use PHPHtmlParser\Options; use Genius257\View\Dom\Parser; +use Exception; +use Stringable; class View { @@ -183,4 +185,33 @@ public function requireToVar($file) require($file); return (string) ob_get_clean(); } + + /** + * Renders the component and returns the resulting content. + */ + public static function renderComponent(Component $component): string + { + ob_start(); + + $returnContent = $component->render(); + $objectBufferContent = ob_get_contents(); + + if ($objectBufferContent === false) { + throw new Exception("ob_get_contents() failed in Component::_render"); + } + + ob_end_clean(); + + if ($returnContent === null) { + $returnContent = ""; + } elseif ($returnContent instanceof Stringable) { + $returnContent = $returnContent->__toString(); + } + + if ($component->trim) { + $returnContent = trim($returnContent); + $objectBufferContent = trim($objectBufferContent); + } + return $objectBufferContent . $returnContent; + } } From f26628bf09c3248f48dc3edec7cbe990d8d52b8f Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 23:13:56 +0000 Subject: [PATCH 10/30] Added new class for components with children --- src/ComponentWithChildren.php | 78 +++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/ComponentWithChildren.php diff --git a/src/ComponentWithChildren.php b/src/ComponentWithChildren.php new file mode 100644 index 0000000..6d8cf64 --- /dev/null +++ b/src/ComponentWithChildren.php @@ -0,0 +1,78 @@ + */ + public array $children = []; + + /** + * @param HtmlNode|Component|Stringable|string $child + */ + public function childToString($child): string + { + if ($child instanceof Stringable) { + return $child->__toString(); + } + + if ($child instanceof HtmlNode) { + return $child->__toString(); + } + + if ($child instanceof Component) { + return View::renderComponent($child); + } + + return strval($child); + } + + /** + * Renders each child and returns the concatenated strings. + * + * @return string + */ + public function renderChildren(): string + { + return implode( + '', + array_map( + function ($child) { + return strval($child); + }, + $this->children + ) + ); + } + + /** + * Get *REAL* HTMLNode children. + * + * *REAL*, meaning that child components would produce a root HTMLNode, + * making changes to the child data appear as not working in some cases. + * + * @return HtmlNode[] + */ + public function getHTMLNodeChildren() + { + $HTMLNodes = []; + $children = $this->properties['children'] ?? []; + foreach ($children as $child) { + if ($child instanceof RootNode) { + foreach ($child->getChildren() as $child) { + if ($child instanceof HtmlNode) { + $HTMLNodes[] = $child; + } + } + } elseif ($child instanceof HtmlNode) { + $HTMLNodes[] = $child; + } + } + + return $HTMLNodes; + } +} From 2bce71b382e511edeb444d2a3cfd254900cd7ce6 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 23:48:29 +0000 Subject: [PATCH 11/30] Added Stringable interface Native Interface not available before 8.0 --- src/Stringable.php | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/Stringable.php diff --git a/src/Stringable.php b/src/Stringable.php new file mode 100644 index 0000000..d881c1c --- /dev/null +++ b/src/Stringable.php @@ -0,0 +1,11 @@ + Date: Wed, 20 Sep 2023 23:48:46 +0000 Subject: [PATCH 12/30] Changed Stringable reference to namespace local --- src/Component.php | 4 ---- src/ComponentWithChildren.php | 1 - src/View.php | 1 - 3 files changed, 6 deletions(-) diff --git a/src/Component.php b/src/Component.php index 9e21b3d..eb69e6a 100644 --- a/src/Component.php +++ b/src/Component.php @@ -2,10 +2,6 @@ namespace Genius257\View; -use PHPHtmlParser\Dom\Node\HtmlNode; -use Genius257\View\Dom\Node\RootNode; -use Stringable; - /** * @method $this setChildren(array $value) */ diff --git a/src/ComponentWithChildren.php b/src/ComponentWithChildren.php index 6d8cf64..7173d66 100644 --- a/src/ComponentWithChildren.php +++ b/src/ComponentWithChildren.php @@ -4,7 +4,6 @@ use Genius257\View\Dom\Node\RootNode; use PHPHtmlParser\Dom\Node\HtmlNode; -use Stringable; abstract class ComponentWithChildren extends Component { diff --git a/src/View.php b/src/View.php index 5855b94..1726d2f 100644 --- a/src/View.php +++ b/src/View.php @@ -8,7 +8,6 @@ use PHPHtmlParser\Options; use Genius257\View\Dom\Parser; use Exception; -use Stringable; class View { From 42f11b488eb9859fffecfa5090defc505131d1c0 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 23:58:35 +0000 Subject: [PATCH 13/30] Fixed typo --- src/Dom/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dom/Parser.php b/src/Dom/Parser.php index ae6024d..49f63f1 100644 --- a/src/Dom/Parser.php +++ b/src/Dom/Parser.php @@ -275,7 +275,7 @@ private function setUpAttributes(Content $content, int $size, HtmlNode $node, Op if ($options->isStrict()) { // can't have this in strict html $character = $content->getPosition(); - throw new StrictException("Tag '$tag' has an attribute '$name' with out a value! (character #$character)"); + throw new StrictException("Tag '$tag' has an attribute '$name' without a value! (character #$character)"); } $node->getTag()->setAttribute($name, null); if ($content->char() != '>') { From 860ac1f4a79e628c9c6db75c98b2e95e47e2f1da Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Wed, 20 Sep 2023 23:59:56 +0000 Subject: [PATCH 14/30] Changed expression across multiple lines to remove the max line length warning from phpcs --- src/Dom/Parser.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Dom/Parser.php b/src/Dom/Parser.php index 49f63f1..0638f3d 100644 --- a/src/Dom/Parser.php +++ b/src/Dom/Parser.php @@ -275,7 +275,9 @@ private function setUpAttributes(Content $content, int $size, HtmlNode $node, Op if ($options->isStrict()) { // can't have this in strict html $character = $content->getPosition(); - throw new StrictException("Tag '$tag' has an attribute '$name' without a value! (character #$character)"); + throw new StrictException( + "Tag '$tag' has an attribute '$name' without a value! (character #$character)" + ); } $node->getTag()->setAttribute($name, null); if ($content->char() != '>') { From e2f0f2667c209f64d427de03c385191dfdaae676 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Thu, 21 Sep 2023 00:13:05 +0000 Subject: [PATCH 15/30] Changed expression across multiple lines to remove the max line length warning from phpcs --- src/Dom/Parser.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Dom/Parser.php b/src/Dom/Parser.php index 0638f3d..24ce93d 100644 --- a/src/Dom/Parser.php +++ b/src/Dom/Parser.php @@ -182,7 +182,9 @@ private function parseTag(Options $options, Content $content, int $size): TagDTO // Should be a self closing tag, check if we are strict if ($options->isStrict()) { $character = $content->getPosition(); - throw new StrictException("Tag '" . $node->getTag()->name() . "' is not self closing! (character #$character)"); + throw new StrictException( + "Tag '" . $node->getTag()->name() . "' is not self closing! (character #$character)" + ); } // We force self closing on this tag. From b350cf1746ec892c48a22d094fa19514d1fa4778 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Thu, 21 Sep 2023 00:41:31 +0000 Subject: [PATCH 16/30] Changed usage of now removed properties class property, to isntad using the actual class property --- src/ComponentWithChildren.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ComponentWithChildren.php b/src/ComponentWithChildren.php index 7173d66..ef59b96 100644 --- a/src/ComponentWithChildren.php +++ b/src/ComponentWithChildren.php @@ -59,8 +59,7 @@ function ($child) { public function getHTMLNodeChildren() { $HTMLNodes = []; - $children = $this->properties['children'] ?? []; - foreach ($children as $child) { + foreach ($this->children as $child) { if ($child instanceof RootNode) { foreach ($child->getChildren() as $child) { if ($child instanceof HtmlNode) { From 4f62efc88c2958333cb720bd4dff9a6a9baca70a Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Thu, 21 Sep 2023 00:42:12 +0000 Subject: [PATCH 17/30] Changed test code to reflect breaking changes made to the code base --- tests/ComponentTest.php | 94 ++++++++---------------------------- tests/testData/Component.php | 2 +- 2 files changed, 22 insertions(+), 74 deletions(-) diff --git a/tests/ComponentTest.php b/tests/ComponentTest.php index b7f5b06..fe3fb7c 100644 --- a/tests/ComponentTest.php +++ b/tests/ComponentTest.php @@ -4,23 +4,22 @@ use ErrorException; use Genius257\View\Component; +use Genius257\View\ComponentWithChildren; use Genius257\View\Dom\Node\HtmlNode; use Genius257\View\Dom\Node\RootNode; +use Genius257\View\View; use PHPUnit\Framework\TestCase; class ComponentTest extends TestCase { public function createComponent() { - return new class () extends Component + return new class () extends ComponentWithChildren { - protected $properties = [ - 'children' => [], - 'style' => 'display:none;', - 'src' => '/image.png', - ]; + public $style = 'display:none;'; + public $src = '/image.png'; - public function _render() + public function render() { return "xyz"; } @@ -40,12 +39,12 @@ public function createComponentWithChildren() $rootNode->addChild($nodeD); $rootNode->addChild($nodeE); - $component->setChildren([ + $component->children = [ $nodeA, $nodeB, "test", $rootNode, - ]); + ]; return $component; } @@ -53,24 +52,19 @@ public function createComponentWithChildren() public function testTrim() { $component = new class () extends Component { - protected $trim = false; + public $trim = false; - public function setTrim(bool $trim): bool - { - return $this->trim = $trim; - } - - public function _render() + public function render() { return " a b c \t\n"; } }; - $this->assertEquals(" a b c \t\n", $component->render()); + $this->assertEquals(" a b c \t\n", View::renderComponent($component)); - $component->setTrim(true); + $component->trim = true; - $this->assertEquals("a b c", $component->render()); + $this->assertEquals("a b c", View::renderComponent($component)); } public function testMake() @@ -96,20 +90,8 @@ public function testGetProperty() { $component = $this->createComponent(); - $this->assertEquals('display:none;', $component->getProperty('style')); - $this->assertEquals('/image.png', $component->getProperty('src')); - - // missing properties, should by default return null - $this->assertEquals(null, $component->getProperty('missing')); - } - - public function testHasProperty() - { - $component = $this->createComponent(); - - $this->assertTrue($component->hasProperty('style')); - $this->assertTrue($component->hasProperty('src')); - $this->assertFalse($component->hasProperty('missing')); + $this->assertEquals('display:none;', $component->style); + $this->assertEquals('/image.png', $component->src); } /** @@ -130,12 +112,12 @@ public function testGetHTMLNodeChildren() $rootNode->addChild($nodeD); $rootNode->addChild($nodeE); - $component->setChildren([ + $component->children = [ $nodeA, $nodeB, "test", $rootNode, - ]); + ]; $children = $component->getHTMLNodeChildren(); @@ -154,16 +136,17 @@ public function testRender() { $component = $this->createComponentWithChildren(); - $this->assertEquals("xyz", $component->render()); + $this->assertEquals("xyz", View::renderComponent($component)); } public function testRenderWarningWithTwoOutputSources() { + $this->markTestSkipped('TODO: implement'); $this->expectException(ErrorException::class); - $this->expectExceptionMessageMatches('/^component .*::_render produced content to the output buffer AND returned a non null value$/'); + $this->expectExceptionMessageMatches('/^component .*::render produced content to the output buffer AND returned a non null value$/'); $component = new class extends Component { - public function _render() + public function render() { echo "xyz"; return "xyz"; @@ -172,39 +155,4 @@ public function _render() $component->render(); } - - public function testMagicSetterMethods() - { - $component = $this->createComponent(); - - $setterReturn = $component->setChildren(["child"]); - - // Assert that the default setter magic method logic returns $this, allowing call chaining. - $this->assertEquals($component, $setterReturn); - - // Assert that the value given to the magic setter method, was applied as the actual new property value. - $this->assertEquals(["child"], $component->getProperty('children')); - } - - public function testGuardsMagicSetterMethods() - { - $component = $this->createComponent(); - - $component->setSrc("test"); - - $this->expectException(\Exception::class); - $this->expectExceptionMessage(sprintf('Component property "missing" is not defined on "%s"', get_class($component))); - - $component->setMissing(); - } - - public function testGuardMagicCallUndefinedMethod() - { - $component = $this->createComponent(); - - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Call to undefined method "missing"'); - - $component->missing(); - } } diff --git a/tests/testData/Component.php b/tests/testData/Component.php index fd5db41..492617f 100644 --- a/tests/testData/Component.php +++ b/tests/testData/Component.php @@ -11,7 +11,7 @@ class Component extends ViewComponent 'data-extra' => null, ]; - protected function _render() + public function render() { return ""; } From 2679edc9089ad23a21902856f86aab847d867c46 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Thu, 21 Sep 2023 14:08:51 +0000 Subject: [PATCH 18/30] Changed getProperties method to work with the new class redesign --- src/Component.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Component.php b/src/Component.php index eb69e6a..52adadc 100644 --- a/src/Component.php +++ b/src/Component.php @@ -35,7 +35,16 @@ public static function make() */ public function getProperties() { - return $this->properties; + $reflectionClass = new \ReflectionClass($this); + $properties = $reflectionClass->getProperties(\ReflectionProperty::IS_PUBLIC); + + $result = []; + + foreach ($properties as $property) { + $result[$property->getName()] = $property->getValue($this); + } + + return $result; } /** From 6c04e087d046f4d113df04c626c6c87730eee577 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Thu, 21 Sep 2023 14:25:44 +0000 Subject: [PATCH 19/30] Changed component documentation --- docs/component.md | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/docs/component.md b/docs/component.md index 7647199..45d01c8 100644 --- a/docs/component.md +++ b/docs/component.md @@ -2,33 +2,28 @@ ## Usage -To use components, simply create a new class that extends from `Genius257\View\Component` and implement the `_render` method. +To use components, simply create a new class that extends from `Genius257\View\Component` and implement the `render` method. ### Attributes -Attributes for a view component are set via the protected property `$properties`. +Attributes for a view component are set via public properties. -It contains an associative array with the key being the attribute name and the value being the default value. - -Components inherit attributes from parents, even when the `$properties` class property value is changed on the child class definition. - -All components have a reserved property key named `"children"`. It contains an array of nested elements within the component html tag. +All components have a reserved property named `"trim"`. It is a boolean value, indicating if the render post process should trim the output. ### Render -The component render output is given via the `_render` method. +The component render output is given via the `render` method. -The method can return one of: -* a string -* Nothing but write directly to PHP output, for example via the echo function. +The method can give output via: +* A return statement. +* Writing to the output buffer, for example via the echo function. -But NOT both. +If both a return and content in the output buffer is given, the final render will be the a concatinated string, with output buffer first, return output second. #### Stringable -casting this class to string, will result in the same as a `render` method call on the component +casting this class to string, will result in the same as making the View class render the component. #### Nested render - The component MAY return or output html with another component tag. That component tag will also be processed in the same view render call, until all component tags are processed. @@ -37,7 +32,7 @@ That component tag will also be processed in the same view render call, until al Component tags are html elements, where the HTML tag are the entire class reference string (Full namespace included) -Component tags MAY have children, if supported by the component referenced. +Component tags MAY have children, if they extend from the ComponentWithChildren class. example: From b5593cca99d881363acd3c19a297f44b10421d51 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Thu, 21 Sep 2023 14:32:41 +0000 Subject: [PATCH 20/30] Changed View constructor to always require file WITH the extension. --- src/View.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/View.php b/src/View.php index 1726d2f..bc2fd51 100644 --- a/src/View.php +++ b/src/View.php @@ -42,12 +42,18 @@ class View /** * Initialize a new View class instance. * - * @param string $view view file path + * @param string $viewFilePath view file path */ - public function __construct(string $view) + public function __construct(string $viewFilePath) { - $this->view = $view; - $this->resolvedView = stream_resolve_include_path($this->view) ? $this->view : $this->view . '.php'; + $resolvedView = stream_resolve_include_path($viewFilePath); + + if ($resolvedView === false) { + throw new Exception("View file not found: {$viewFilePath}"); + } + + $this->view = $viewFilePath; + $this->resolvedView = $resolvedView; $this->viewContent = $this->requireToVar($this->resolvedView); } From 13ad5c289c002f053eb9c395b83690b90c5f994a Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Thu, 21 Sep 2023 14:40:14 +0000 Subject: [PATCH 21/30] Changed code styling based on phpcs --- src/View.php | 7 ++++++- tests/ComponentTest.php | 9 +++++++-- tests/ViewTest.php | 14 +++++++++++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/View.php b/src/View.php index bc2fd51..214a274 100644 --- a/src/View.php +++ b/src/View.php @@ -123,7 +123,12 @@ protected function processNode($node) return $node; } } catch (\Throwable $throwable) { - $exception = new ProcessNodeException($throwable, realpath($this->resolvedView), $className ?? $node->tag->name(), is_null($node ?? null) ? null : $node->getLocation()); + $exception = new ProcessNodeException( + $throwable, + realpath($this->resolvedView), + $className ?? $node->tag->name(), + is_null($node ?? null) ? null : $node->getLocation() + ); throw $exception; } diff --git a/tests/ComponentTest.php b/tests/ComponentTest.php index fe3fb7c..8907268 100644 --- a/tests/ComponentTest.php +++ b/tests/ComponentTest.php @@ -83,7 +83,10 @@ public function testGetProperties() { $component = $this->createComponent(); - $this->assertEquals(['style' => 'display:none;', 'src' => '/image.png', 'children' => []], $component->getProperties()); + $this->assertEquals( + ['style' => 'display:none;', 'src' => '/image.png', 'children' => []], + $component->getProperties() + ); } public function testGetProperty() @@ -143,7 +146,9 @@ public function testRenderWarningWithTwoOutputSources() { $this->markTestSkipped('TODO: implement'); $this->expectException(ErrorException::class); - $this->expectExceptionMessageMatches('/^component .*::render produced content to the output buffer AND returned a non null value$/'); + $this->expectExceptionMessageMatches( + '/^component .*::render produced content to the output buffer AND returned a non null value$/' + ); $component = new class extends Component { public function render() diff --git a/tests/ViewTest.php b/tests/ViewTest.php index f9da30c..304b9ba 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -27,7 +27,9 @@ public function testFromFileWithRelativePath() public function testParse() { $view = $this->createView(); - $dom = $view->parse('link text'); + $dom = $view->parse( + 'link text' + ); $this->assertEquals('', $dom->__toString()); } @@ -35,7 +37,10 @@ public function testParse() public function testRender() { $view = $this->createView(); - $this->assertEquals("\n\n \n \n text content\n
\n \n \n\n", $view->render()); + $this->assertEquals( + "\n\n \n \n text content\n
\n \n \n\n", + $view->render() + ); } /** @@ -85,6 +90,9 @@ public function testRequireToVar() { $view = $this->createView(); $data = $view->requireToVar(__DIR__ . '/testData/view.php'); - $this->assertEquals("\n\n \n \n text content\n
\n \n \n\n", $data); + $this->assertEquals( + "\n\n \n \n text content\n
\n \n \n\n", + $data + ); } } From efcbfa16b92d9aa62a14d323a387555acfa11602 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Thu, 21 Sep 2023 14:59:03 +0000 Subject: [PATCH 22/30] Changed component tostring method to be more simplified after codebase changes --- src/Component.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Component.php b/src/Component.php index 52adadc..bcf08e6 100644 --- a/src/Component.php +++ b/src/Component.php @@ -55,12 +55,6 @@ abstract public function render(); public function __toString(): string { - $rendered = View::renderComponent($this); - - if ($rendered instanceof Stringable) { - return $rendered->__toString(); - } - - return $rendered; + return View::renderComponent($this); } } From 5ac9fa13f71ec743774ac60ef12369ec3c0d738f Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Thu, 21 Sep 2023 14:59:51 +0000 Subject: [PATCH 23/30] Removed return phpdoc tag --- src/Dom/Parser.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Dom/Parser.php b/src/Dom/Parser.php index 24ce93d..5802edd 100644 --- a/src/Dom/Parser.php +++ b/src/Dom/Parser.php @@ -213,8 +213,6 @@ private function parseTag(Options $options, Content $content, int $size): TagDTO * @param HtmlNode $node * @param Options $options * @param string|Tag $tag - * - * @return void */ private function setUpAttributes(Content $content, int $size, HtmlNode $node, Options $options, $tag): void { From 7dcf958a56c43df83b972dc7c75f1cd58f69b134 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Thu, 21 Sep 2023 15:00:13 +0000 Subject: [PATCH 24/30] Added null check safeguard --- src/Dom/Parser.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Dom/Parser.php b/src/Dom/Parser.php index 5802edd..c6706ed 100644 --- a/src/Dom/Parser.php +++ b/src/Dom/Parser.php @@ -94,11 +94,13 @@ public function parse(Options $options, Content $content, int $size): AbstractNo /** @var \PHPHtmlParser\Dom\Node\HtmlNode|null $node */ $node = $tagDTO->getNode(); - $activeNode->addChild($node); + if ($node !== null) { + $activeNode->addChild($node); - // check if node is self closing - if (!$node->getTag()->isSelfClosing()) { - $activeNode = $node; + // check if node is self closing + if (!$node->getTag()->isSelfClosing()) { + $activeNode = $node; + } } } elseif ( $options->isWhitespaceTextNode() || From ffecd5d18e3b4441f09554d27e0d00aaa6cd0911 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Sun, 24 Sep 2023 22:02:51 +0000 Subject: [PATCH 25/30] Added check for output buffer level, post component render. --- src/View.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/View.php b/src/View.php index 214a274..d3843a3 100644 --- a/src/View.php +++ b/src/View.php @@ -203,6 +203,8 @@ public static function renderComponent(Component $component): string { ob_start(); + $level = ob_get_level(); + $returnContent = $component->render(); $objectBufferContent = ob_get_contents(); @@ -210,6 +212,10 @@ public static function renderComponent(Component $component): string throw new Exception("ob_get_contents() failed in Component::_render"); } + if ($level <> ob_get_level()) { + throw new Exception("output buffer nesting level mismatch. Expected: " . $level . ", got: " . ob_get_level()); + } + ob_end_clean(); if ($returnContent === null) { From 3a6ad44c2d9374a909a687ed36a4ff3b9c8e1da2 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Sun, 24 Sep 2023 22:04:49 +0000 Subject: [PATCH 26/30] Changed phpunit dependency version Upgrade needed to allow coverage to work as expected. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 975e667..eff82f8 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "paquettg/php-html-parser": "~3.1.1" }, "require-dev": { - "phpunit/phpunit": "^8.5", + "phpunit/phpunit": "^9.0", "phpstan/phpstan": "^1.9", "squizlabs/php_codesniffer": "^3.7" }, From 981dd60b40c44942c7dba95f3882868b14e5ca87 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Sun, 24 Sep 2023 22:06:07 +0000 Subject: [PATCH 27/30] Added composer scripts to automate local phpunit coverage checks --- composer.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/composer.json b/composer.json index eff82f8..cc75fa2 100755 --- a/composer.json +++ b/composer.json @@ -31,5 +31,9 @@ "App\\": "example/app/", "Tests\\": "tests/" } + }, + "scripts": { + "phpunit-coverage": "XDEBUG_MODE=coverage phpunit --coverage-text", + "phpunit-coverage-html": "XDEBUG_MODE=coverage phpunit --coverage-html=coverage" } } From fee7a68e5169750894dfeb89160cc53bcb8314ef Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Sun, 24 Sep 2023 22:07:51 +0000 Subject: [PATCH 28/30] Changed childToString to static --- src/ComponentWithChildren.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ComponentWithChildren.php b/src/ComponentWithChildren.php index ef59b96..b3824a6 100644 --- a/src/ComponentWithChildren.php +++ b/src/ComponentWithChildren.php @@ -13,7 +13,7 @@ abstract class ComponentWithChildren extends Component /** * @param HtmlNode|Component|Stringable|string $child */ - public function childToString($child): string + public static function childToString($child): string { if ($child instanceof Stringable) { return $child->__toString(); From 7a7c308e09f6dd6313a355dd4540318cfe2e9f8d Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Sun, 24 Sep 2023 22:09:32 +0000 Subject: [PATCH 29/30] Changed unit tests to fill test coverage to an acceptable percent. --- tests/ComponentTest.php | 117 ++++++-------------- tests/ComponentWithChildrenTest.php | 140 +++++++++++++++++++++++ tests/Dom/HtmlNodeTest.php | 16 ++- tests/Dom/LocationTest.php | 12 ++ tests/Dom/ParserTest.php | 54 +++++++++ tests/ProcessNodeExceptionTest.php | 6 + tests/ViewTest.php | 165 ++++++++++++++++++++++++++++ 7 files changed, 427 insertions(+), 83 deletions(-) create mode 100644 tests/ComponentWithChildrenTest.php create mode 100644 tests/Dom/ParserTest.php diff --git a/tests/ComponentTest.php b/tests/ComponentTest.php index 8907268..cce12e9 100644 --- a/tests/ComponentTest.php +++ b/tests/ComponentTest.php @@ -12,6 +12,9 @@ class ComponentTest extends TestCase { + /** + * @coversNothing + */ public function createComponent() { return new class () extends ComponentWithChildren @@ -26,29 +29,9 @@ public function render() }; } - public function createComponentWithChildren() - { - $component = $this->createComponent(); - - $nodeA = new HtmlNode("a"); - $nodeB = new \PHPHtmlParser\Dom\Node\HtmlNode("b"); - $rootNode = new RootNode('c'); - $nodeD = new HtmlNode('d'); - $nodeE = new HtmlNode('e'); - - $rootNode->addChild($nodeD); - $rootNode->addChild($nodeE); - - $component->children = [ - $nodeA, - $nodeB, - "test", - $rootNode, - ]; - - return $component; - } - + /** + * @covers \Genius257\View\Component + */ public function testTrim() { $component = new class () extends Component { @@ -67,89 +50,49 @@ public function render() $this->assertEquals("a b c", View::renderComponent($component)); } + /** + * @covers \Genius257\View\Component::make + */ public function testMake() { $component = $this->createComponent(); $this->assertInstanceOf(get_class($component), $component::make()); - - $this->expectError(); - $this->expectErrorMessage("Cannot instantiate abstract class Genius257\View\Component"); - - Component::make(); } + /** + * @covers \Genius257\View\Component::getProperties + */ public function testGetProperties() { $component = $this->createComponent(); $this->assertEquals( - ['style' => 'display:none;', 'src' => '/image.png', 'children' => []], + [ + 'style' => 'display:none;', + 'src' => '/image.png', + 'children' => [], + 'trim' => true, + ], $component->getProperties() ); } - public function testGetProperty() - { - $component = $this->createComponent(); - - $this->assertEquals('display:none;', $component->style); - $this->assertEquals('/image.png', $component->src); - } - /** - * Test that items that are not itanceof HtmlNode are filtered out - * and RootNode instances are excluded, but RootNode children gets extracted. - * @return void + * @covers \Genius257\View\Component::render */ - public function testGetHTMLNodeChildren() - { - $component = $this->createComponent(); - - $nodeA = new HtmlNode("a"); - $nodeB = new \PHPHtmlParser\Dom\Node\HtmlNode("b"); - $rootNode = new RootNode('c'); - $nodeD = new HtmlNode('d'); - $nodeE = new HtmlNode('e'); - - $rootNode->addChild($nodeD); - $rootNode->addChild($nodeE); - - $component->children = [ - $nodeA, - $nodeB, - "test", - $rootNode, - ]; - - $children = $component->getHTMLNodeChildren(); - - $this->assertCount(4, $children); - $this->assertEquals([$nodeA, $nodeB, $nodeD, $nodeE], $children); - } - - public function testRenderChildren() - { - $component = $this->createComponentWithChildren(); - - $this->assertEquals("test", $component->renderChildren()); - } - public function testRender() { - $component = $this->createComponentWithChildren(); + $component = $this->createComponent(); $this->assertEquals("xyz", View::renderComponent($component)); } - public function testRenderWarningWithTwoOutputSources() + /** + * @covers \Genius257\View\Component::render + */ + public function testRenderWithTwoOutputSources() { - $this->markTestSkipped('TODO: implement'); - $this->expectException(ErrorException::class); - $this->expectExceptionMessageMatches( - '/^component .*::render produced content to the output buffer AND returned a non null value$/' - ); - $component = new class extends Component { public function render() { @@ -158,6 +101,16 @@ public function render() } }; - $component->render(); + $this->assertEquals("xyzxyz", View::renderComponent($component)); + } + + /** + * @covers \Genius257\View\Component::__toString + */ + public function testToString() + { + $component = $this->createComponent(); + + $this->assertEquals("xyz", $component->__toString()); } } diff --git a/tests/ComponentWithChildrenTest.php b/tests/ComponentWithChildrenTest.php new file mode 100644 index 0000000..01e951e --- /dev/null +++ b/tests/ComponentWithChildrenTest.php @@ -0,0 +1,140 @@ +addChild($nodeD); + $rootNode->addChild($nodeE); + + $component->children = [ + $nodeA, + $nodeB, + "test", + $rootNode, + ]; + + return $component; + } + + /** + * @covers \Genius257\View\ComponentWithChildren::childToString + */ + public function testChildToStringWithHtmlNode() + { + $htmlNode = new \PHPHtmlParser\Dom\Node\HtmlNode('div'); + $htmlNode->addChild(new TextNode('HtmlNode test')); + $this->assertEquals('
HtmlNode test
', ComponentWithChildren::childToString($htmlNode)); + } + + /** + * @covers \Genius257\View\ComponentWithChildren::childToString + */ + public function testChildToStringWithComponent() + { + $component = new class () extends Component { + public $trim = false; + + public function render() + { + echo 'component'; + return ' test'; + } + }; + $this->assertEquals('component test', ComponentWithChildren::childToString($component)); + } + + /** + * @covers \Genius257\View\ComponentWithChildren::childToString + */ + public function testChildToStringWithStringable() + { + $stringable = new class () implements Stringable + { + public function __toString(): string + { + return 'stringable test'; + } + }; + $this->assertEquals('stringable test', ComponentWithChildren::childToString($stringable)); + } + + /** + * @covers \Genius257\View\ComponentWithChildren::childToString + */ + public function testChildToStringWithString() + { + $string = "string test"; + $this->assertEquals($string, ComponentWithChildren::childToString($string)); + } + + /** + * @covers \Genius257\View\ComponentWithChildren::renderChildren + */ + public function testRenderChildren() + { + $component = $this->createComponentWithChildren(); + + $this->assertEquals("test", $component->renderChildren()); + } + + /** + * Test that items that are not itanceof HtmlNode are filtered out + * and RootNode instances are excluded, but RootNode children gets extracted. + * + * @covers \Genius257\View\ComponentWithChildren::getHTMLNodeChildren + */ + public function testGetHTMLNodeChildren() + { + $component = $this->createComponentWithChildren(); + + $nodeA = new HtmlNode("a"); + $nodeB = new \PHPHtmlParser\Dom\Node\HtmlNode("b"); + $rootNode = new RootNode('c'); + $nodeD = new HtmlNode('d'); + $nodeE = new HtmlNode('e'); + + $rootNode->addChild($nodeD); + $rootNode->addChild($nodeE); + + $component->children = [ + $nodeA, + $nodeB, + "test", + $rootNode, + ]; + + $children = $component->getHTMLNodeChildren(); + + $this->assertCount(4, $children); + $this->assertEquals([$nodeA, $nodeB, $nodeD, $nodeE], $children); + } +} diff --git a/tests/Dom/HtmlNodeTest.php b/tests/Dom/HtmlNodeTest.php index 68f8f95..2a27907 100644 --- a/tests/Dom/HtmlNodeTest.php +++ b/tests/Dom/HtmlNodeTest.php @@ -8,14 +8,28 @@ class HtmlNodeTest extends TestCase { + /** + * @covers \Genius257\View\Dom\Node\HtmlNode::rawTag + * @covers \Genius257\View\Dom\Node\HtmlNode::__construct + */ public function testRawTag() { $tag = 'DIV'; $node = new HtmlNode($tag, $tag, null); - $this->assertEquals($tag, $node->rawTag()); + + $tag = new \PHPHtmlParser\Dom\Tag('tag-name'); + $node = new HtmlNode($tag, null, null); + $this->assertEquals('tag-name', $node->rawTag()); + + $node = new HtmlNode($tag, 'raw-tag-name', null); + $this->assertEquals('raw-tag-name', $node->rawTag()); } + /** + * @covers \Genius257\View\Dom\Node\HtmlNode::getLocation + * @covers \Genius257\View\Dom\Node\HtmlNode::__construct + */ public function testLocationGetter() { $tag = 'DIV'; diff --git a/tests/Dom/LocationTest.php b/tests/Dom/LocationTest.php index becfc8e..d3f5968 100644 --- a/tests/Dom/LocationTest.php +++ b/tests/Dom/LocationTest.php @@ -7,6 +7,10 @@ class LocationTest extends TestCase { + /** + * @covers \Genius257\View\Dom\Location::getLine + * @covers \Genius257\View\Dom\Location::__construct + */ public function testLineGetter() { $location = new Location(1, 2, 3); @@ -14,6 +18,10 @@ public function testLineGetter() $this->assertEquals(1, $location->getLine()); } + /** + * @covers \Genius257\View\Dom\Location::getColumn + * @covers \Genius257\View\Dom\Location::__construct + */ public function testColumnGetter() { $location = new Location(1, 2, 3); @@ -21,6 +29,10 @@ public function testColumnGetter() $this->assertEquals(2, $location->getColumn()); } + /** + * @covers \Genius257\View\Dom\Location::getOffset + * @covers \Genius257\View\Dom\Location::__construct + */ public function testOffsetGetter() { $location = new Location(1, 2, 3); diff --git a/tests/Dom/ParserTest.php b/tests/Dom/ParserTest.php new file mode 100644 index 0000000..302807e --- /dev/null +++ b/tests/Dom/ParserTest.php @@ -0,0 +1,54 @@ +getLocation($content); + $this->assertSame(0, $location->getOffset()); + $this->assertSame(1, $location->getLine()); + $this->assertSame(0, $location->getColumn()); + + $content->copyUntil('b'); + $content->fastForward(1); + + $location = $parser->getLocation($content); + $this->assertSame(3, $location->getOffset()); + $this->assertSame(2, $location->getLine()); + $this->assertSame(1, $location->getColumn()); + } + + /** + * @covers \Genius257\View\Dom\Parser::parse + */ + public function testParse() + { + $parser = new Parser(); + + $html = '
test
<>'; + + $parsed = $parser->parse(new Options(), new Content($html), 10); + + $this->assertInstanceOf(RootNode::class, $parsed); + + $this->assertSame( + '
test
', + $parsed->outerHtml() + ); + } +} diff --git a/tests/ProcessNodeExceptionTest.php b/tests/ProcessNodeExceptionTest.php index 863beeb..b4d8111 100644 --- a/tests/ProcessNodeExceptionTest.php +++ b/tests/ProcessNodeExceptionTest.php @@ -8,6 +8,9 @@ class ProcessNodeExceptionTest extends TestCase { + /** + * @covers \Genius257\View\ProcessNodeException + */ public function testTraceWithLocation() { $exception = new \Exception(); @@ -24,6 +27,9 @@ public function testTraceWithLocation() $this->assertEquals($expected, $processNodeException->getTrace()); } + /** + * @covers \Genius257\View\ProcessNodeException + */ public function testTraceWithoutLocation() { $exception = new \Exception(); diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 304b9ba..b8c63db 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -24,6 +24,31 @@ public function testFromFileWithRelativePath() } */ + /** + * @covers \Genius257\View\View::__construct + */ + public function testConstruct() + { + $view = $this->createView(); + + // This test only checks that the constructor works, not that it does anything. + $this->assertEquals(1, 1); + } + + /** + * @covers \Genius257\View\View::__construct + */ + public function testConstructWithInvalidFile() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('View file not found: ' . __DIR__ . '/view/does/not/exist.php'); + + new View(__DIR__ . '/view/does/not/exist.php'); + } + + /** + * @covers \Genius257\View\View::parse + */ public function testParse() { $view = $this->createView(); @@ -34,6 +59,9 @@ public function testParse() $this->assertEquals('', $dom->__toString()); } + /** + * @covers \Genius257\View\View::render + */ public function testRender() { $view = $this->createView(); @@ -49,6 +77,8 @@ public function testRender() * This method checks that a view will use the viewCache * on subsequent render calls, ignoring the viewContent * after first render. + * + * @covers \Genius257\View\View::render */ public function testRenderWithCache() { @@ -64,6 +94,9 @@ public function testRenderWithCache() $this->assertEquals($expected, $view->render()); } + /** + * @covers \Genius257\View\View::forceRender + */ public function testForceRender() { $view = $this->createView(); @@ -86,6 +119,9 @@ public function testForceRender() $this->assertEquals('123', $view->render()); } + /** + * @covers \Genius257\View\View::requireToVar + */ public function testRequireToVar() { $view = $this->createView(); @@ -95,4 +131,133 @@ public function testRequireToVar() $data ); } + + /** + * @covers \Genius257\View\View::renderComponent + */ + public function testRenderComponentWithTrim() + { + $component = new class () extends \Genius257\View\Component { + public $trim = true; + + public function render() + { + echo ' some '; + return ' content '; + } + }; + + $this->assertEquals("somecontent", View::renderComponent($component)); + + $component->trim = false; + $this->assertEquals(" some content ", View::renderComponent($component)); + } + + /** + * @covers \Genius257\View\View::renderComponent + */ + public function testRenderComponent() + { + $component = new class () extends \Genius257\View\Component { + + public function render() + { + echo 'abc'; + return 'def'; + } + }; + + $this->assertEquals( + "abcdef", + View::renderComponent($component) + ); + } + + /** + * @covers \Genius257\View\View::renderComponent + */ + public function testRenderComponentWithClosedObjectBuffer() + { + $output_buffer_level = ob_get_level(); + + $component = new class () extends \Genius257\View\Component { + public function render() + { + // phpunit and other may add additional ob levels + while (ob_get_level() > 0) { + ob_end_clean(); + } + } + }; + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('ob_get_contents() failed in Component::_render'); + + try { + View::renderComponent($component); + } finally { + // restore ob levels + while (ob_get_level() < $output_buffer_level) { + ob_start(); + } + } + } + + /** + * @covers \Genius257\View\View::renderComponent + */ + public function testRenderComponentWithMismatchedObjectBufferLevel() + { + $component = new class () extends \Genius257\View\Component { + public function render() + { + ob_end_clean(); + } + }; + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('output buffer nesting level mismatch. Expected: 2, got: 1'); + + View::renderComponent($component); + } + + /** + * @covers \Genius257\View\View::renderComponent + */ + public function testRenderComponentWithoutReturnFromRender() + { + $component = new class () extends \Genius257\View\Component { + public function render() + { + } + }; + + $this->assertEquals( + "", + View::renderComponent($component) + ); + } + + /** + * @covers \Genius257\View\View::renderComponent + */ + public function testRenderComponentWithStringableReturnFromRender() + { + $component = new class () extends \Genius257\View\Component { + public function render() + { + return new class () implements \Genius257\View\Stringable { + public function __toString() + { + return 'abc'; + } + }; + } + }; + + $this->assertEquals( + "abc", + View::renderComponent($component) + ); + } } From 90dc6a219f242e364bc106037c7c1a31d4964ad9 Mon Sep 17 00:00:00 2001 From: Anders Pedersen Date: Sun, 24 Sep 2023 22:44:58 +0000 Subject: [PATCH 30/30] Updated versions to match new requirements --- .github/workflows/codacy-coverage-reporter.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codacy-coverage-reporter.yml b/.github/workflows/codacy-coverage-reporter.yml index ee7d042..cab9b31 100644 --- a/.github/workflows/codacy-coverage-reporter.yml +++ b/.github/workflows/codacy-coverage-reporter.yml @@ -11,14 +11,14 @@ jobs: - name: Composer (php-actions) uses: php-actions/composer@v6 with: - php_version: "7.2" + php_version: "7.4" version: 2.x - name: PHPUnit (php-actions) uses: php-actions/phpunit@v3 with: configuration: phpunit.xml - version: "8.5" - php_version: "7.2" + version: "9.6" + php_version: "7.4" php_extensions: "xdebug mbstring" args: --coverage-clover phpunit-clover.xml env: