From 406b261ef4607a6e63746623aa6c323c6b745ae0 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Sun, 28 Apr 2024 20:26:57 +0100 Subject: [PATCH] feature: pass selectors instead of element references (#494) * feature: pass selectors instead of element references closes #485 * test: extend test coverage to DocumentBinder --- src/Binder.php | 12 +- src/ComponentBinder.php | 10 +- src/DocumentBinder.php | 36 ++++- test/phpunit/ComponentBinderTest.php | 142 ++++++++++++++++++++ test/phpunit/DocumentBinderTest.php | 138 +++++++++++++++++++ test/phpunit/TestHelper/HTMLPageContent.php | 16 +++ 6 files changed, 341 insertions(+), 13 deletions(-) diff --git a/src/Binder.php b/src/Binder.php index 8cffba7..d71c9a0 100644 --- a/src/Binder.php +++ b/src/Binder.php @@ -6,7 +6,7 @@ abstract class Binder { abstract public function bindValue( mixed $value, - ?Element $context = null + null|string|Element $context = null ):void; /** @@ -16,7 +16,7 @@ abstract public function bindValue( abstract public function bindKeyValue( string $key, mixed $value, - ?Element $context = null + null|string|Element $context = null ):void; /** @@ -25,12 +25,12 @@ abstract public function bindKeyValue( */ abstract public function bindData( mixed $kvp, - ?Element $context = null + null|string|Element $context = null ):void; abstract public function bindTable( mixed $tableData, - ?Element $context = null, + null|string|Element $context = null, ?string $bindKey = null ):void; @@ -39,7 +39,7 @@ abstract public function bindTable( */ abstract public function bindList( iterable $listData, - ?Element $context = null, + null|string|Element $context = null, ?string $templateName = null ):int; @@ -47,7 +47,7 @@ abstract public function bindList( abstract public function bindListCallback( iterable $listData, callable $callback, - ?Element $context = null, + null|string|Element $context = null, ?string $templateName = null ):int; } diff --git a/src/ComponentBinder.php b/src/ComponentBinder.php index cce543e..c9e8eaf 100644 --- a/src/ComponentBinder.php +++ b/src/ComponentBinder.php @@ -13,9 +13,13 @@ public function setComponentBinderDependencies(Element $componentElement):void { public function bindList( iterable $listData, - ?Element $context = null, + null|string|Element $context = null, ?string $templateName = null ):int { + if(is_string($context)) { + $context = $this->stringToContext($context); + } + if($context) { $this->checkElementContainedWithinComponent($context); } @@ -53,4 +57,8 @@ private function checkElementContainedWithinComponent(Element $context):void { ); } } + + protected function stringToContext(string $context):Element { + return $this->componentElement->querySelector($context); + } } diff --git a/src/DocumentBinder.php b/src/DocumentBinder.php index e953786..881444f 100644 --- a/src/DocumentBinder.php +++ b/src/DocumentBinder.php @@ -39,8 +39,12 @@ public function setDependencies( */ public function bindValue( mixed $value, - ?Element $context = null + null|string|Element $context = null ):void { + if(is_string($context)) { + $context = $this->stringToContext($context); + } + $this->bind(null, $value, $context); } @@ -51,8 +55,12 @@ public function bindValue( public function bindKeyValue( string $key, mixed $value, - ?Element $context = null + null|Element|string $context = null, ):void { + if(is_string($context)) { + $context = $this->stringToContext($context); + } + $this->bind($key, $value, $context); } @@ -62,8 +70,12 @@ public function bindKeyValue( */ public function bindData( mixed $kvp, - ?Element $context = null + null|string|Element $context = null ):void { + if(is_string($context)) { + $context = $this->stringToContext($context); + } + if($this->isIndexedArray($kvp)) { throw new IncompatibleBindDataException("bindData is only compatible with key-value-pair data, but it was passed an indexed array."); } @@ -94,9 +106,13 @@ public function bindData( public function bindTable( mixed $tableData, - ?Element $context = null, + null|string|Element $context = null, ?string $bindKey = null ):void { + if(is_string($context)) { + $context = $this->stringToContext($context); + } + $this->tableBinder->bindTableData( $tableData, $context ?? $this->document, @@ -109,9 +125,13 @@ public function bindTable( */ public function bindList( iterable $listData, - ?Element $context = null, + null|string|Element $context = null, ?string $templateName = null ):int { + if(is_string($context)) { + $context = $this->stringToContext($context); + } + if(!$context) { $context = $this->document; } @@ -123,7 +143,7 @@ public function bindList( public function bindListCallback( iterable $listData, callable $callback, - ?Element $context = null, + null|string|Element $context = null, ?string $templateName = null ):int { if(!$context) { @@ -192,4 +212,8 @@ private function isIndexedArray(mixed $data):bool { return true; } + + protected function stringToContext(string $context):Element { + return $this->document->querySelector($context); + } } diff --git a/test/phpunit/ComponentBinderTest.php b/test/phpunit/ComponentBinderTest.php index 8e37e2d..094e622 100644 --- a/test/phpunit/ComponentBinderTest.php +++ b/test/phpunit/ComponentBinderTest.php @@ -1,5 +1,6 @@ setComponentBinderDependencies($componentElement); $sut->bindKeyValue("listTitle", "This should change!"); } + + public function testBindKeyValue_stringContext():void { + $document = new HTMLDocument(HTMLPageContent::HTML_COMPONENT_WITH_ATTRIBUTE_NESTED); + $componentElement = $document->querySelector("example-component"); + $subComponent1 = $document->querySelector("#subcomponent-1"); + $subComponent2 = $document->querySelector("#subcomponent-2"); + + $elementBinder = self::createMock(ElementBinder::class); + $bindMatcher = self::exactly(3); + $elementBinder->expects($bindMatcher) + ->method("bind") + ->willReturnCallback(function(string $key, string $value, Element $element)use($bindMatcher, $componentElement, $subComponent1, $subComponent2):void { + match($bindMatcher->numberOfInvocations()) { + 1 => self::assertEquals(["title", "Title 1!", $subComponent1], [$key, $value, $element]), + 2 => self::assertEquals(["title", "Title 2!", $subComponent2], [$key, $value, $element]), + 3 => self::assertEquals(["title", "Main title!", $componentElement], [$key, $value, $element]), + }; + }); + + $sut = new ComponentBinder($document); + $sut->setDependencies( + $elementBinder, + self::createMock(PlaceholderBinder::class), + self::createMock(TableBinder::class), + self::createMock(ListBinder::class), + self::createMock(ListElementCollection::class), + self::createMock(BindableCache::class), + ); + $sut->setComponentBinderDependencies($componentElement); + + $sut->bindKeyValue("title", "Title 1!", "#subcomponent-1"); + $sut->bindKeyValue("title", "Title 2!", "#subcomponent-2"); + $sut->bindKeyValue("title", "Main title!"); + } + + public function testBindData_stringContext():void { + $document = new HTMLDocument(HTMLPageContent::HTML_COMPONENT_WITH_ATTRIBUTE_NESTED); + $componentElement = $document->querySelector("example-component"); + $subComponent1 = $document->querySelector("#subcomponent-1"); + $subComponent2 = $document->querySelector("#subcomponent-2"); + + $elementBinder = self::createMock(ElementBinder::class); + $bindMatcher = self::exactly(6); + $elementBinder->expects($bindMatcher) + ->method("bind") + ->willReturnCallback(function(string $key, string $value, Element $element)use($bindMatcher, $componentElement, $subComponent1, $subComponent2):void { + match($bindMatcher->numberOfInvocations()) { + 1 => self::assertEquals(["title", "Title 1!", $subComponent1], [$key, $value, $element]), + 2 => self::assertEquals(["number", "1", $subComponent1], [$key, $value, $element]), + 3 => self::assertEquals(["title", "Title 2!", $subComponent2], [$key, $value, $element]), + 4 => self::assertEquals(["number", "2", $subComponent2], [$key, $value, $element]), + 5 => self::assertEquals(["title", "Main title!", $componentElement], [$key, $value, $element]), + 6 => self::assertEquals(["number", "3", $componentElement], [$key, $value, $element]), + }; + }); + + $sut = new ComponentBinder($document); + $sut->setDependencies( + $elementBinder, + self::createMock(PlaceholderBinder::class), + self::createMock(TableBinder::class), + self::createMock(ListBinder::class), + self::createMock(ListElementCollection::class), + self::createMock(BindableCache::class), + ); + $sut->setComponentBinderDependencies($componentElement); + + $sut->bindData(["title" => "Title 1!", "number" => "1"], "#subcomponent-1"); + $sut->bindData(["title" => "Title 2!", "number" => "2"], "#subcomponent-2"); + $sut->bindData(["title" => "Main title!", "number" => "3"]); + } + + public function testBindList_stringContext():void { + $document = new HTMLDocument(HTMLPageContent::HTML_COMPONENT_WITH_ATTRIBUTE_NESTED); + $componentElement = $document->querySelector("example-component"); + $subComponent1 = $document->querySelector("#subcomponent-1"); + $subComponent2 = $document->querySelector("#subcomponent-2"); + + $listBinder = self::createMock(ListBinder::class); + $bindMatcher = self::exactly(3); + $listBinder->expects($bindMatcher) + ->method("bindListData") + ->willReturnCallback(function(array $listData, Element $context)use($bindMatcher, $componentElement, $subComponent1, $subComponent2):int { + match($bindMatcher->numberOfInvocations()) { + 1 => self::assertEquals([["List", "for", "component 2"], $subComponent2], [$listData, $context]), + 2 => self::assertEquals([["List", "for", "component 1"], $subComponent1], [$listData, $context]), + 3 => self::assertEquals([["List", "for", "main component"], $componentElement], [$listData, $context]), + }; + + return 0; + }); + + $sut = new ComponentBinder($document); + $sut->setDependencies( + self::createMock(ElementBinder::class), + self::createMock(PlaceholderBinder::class), + self::createMock(TableBinder::class), + $listBinder, + self::createMock(ListElementCollection::class), + self::createMock(BindableCache::class), + ); + $sut->setComponentBinderDependencies($componentElement); + + $sut->bindList(["List", "for", "component 2"], "#subcomponent-2"); + $sut->bindList(["List", "for", "component 1"], "#subcomponent-1"); + $sut->bindList(["List", "for", "main component"]); + } + + public function testBindValue_stringContext():void { + $document = new HTMLDocument(HTMLPageContent::HTML_COMPONENT_WITH_ATTRIBUTE_NESTED); + $componentElement = $document->querySelector("example-component"); + $subComponent1 = $document->querySelector("#subcomponent-1"); + $subComponent2 = $document->querySelector("#subcomponent-2"); + + $elementBinder = self::createMock(ElementBinder::class); + $bindMatcher = self::exactly(3); + $elementBinder->expects($bindMatcher) + ->method("bind") + ->willReturnCallback(function(?string $key, string $value, Element $element)use($bindMatcher, $componentElement, $subComponent1, $subComponent2):void { + match($bindMatcher->numberOfInvocations()) { + 1 => self::assertEquals([null, "1", $subComponent1], [$key, $value, $element]), + 2 => self::assertEquals([null, "2", $subComponent2], [$key, $value, $element]), + 3 => self::assertEquals([null, "3", $componentElement], [$key, $value, $element]), + }; + }); + + $sut = new ComponentBinder($document); + $sut->setDependencies( + $elementBinder, + self::createMock(PlaceholderBinder::class), + self::createMock(TableBinder::class), + self::createMock(ListBinder::class), + self::createMock(ListElementCollection::class), + self::createMock(BindableCache::class), + ); + $sut->setComponentBinderDependencies($componentElement); + + $sut->bindValue("1", "#subcomponent-1"); + $sut->bindValue("2", "#subcomponent-2"); + $sut->bindValue("3"); + } } diff --git a/test/phpunit/DocumentBinderTest.php b/test/phpunit/DocumentBinderTest.php index 134dd03..600f4dc 100644 --- a/test/phpunit/DocumentBinderTest.php +++ b/test/phpunit/DocumentBinderTest.php @@ -4,6 +4,7 @@ use DateInterval; use Exception; +use Gt\Dom\Document; use Gt\Dom\Element; use Gt\Dom\HTMLCollection; use Gt\Dom\HTMLDocument; @@ -1400,6 +1401,143 @@ public function testBindList_arrayIterator():void { } } + public function testBindKeyValue_stringContext():void { + $document = new HTMLDocument(HTMLPageContent::HTML_COMPONENT_WITH_ATTRIBUTE_NESTED); + $documentElement = $document->documentElement; + $subComponent1 = $document->querySelector("#subcomponent-1"); + $subComponent2 = $document->querySelector("#subcomponent-2"); + + $elementBinder = self::createMock(ElementBinder::class); + $bindMatcher = self::exactly(3); + $elementBinder->expects($bindMatcher) + ->method("bind") + ->willReturnCallback(function(string $key, string $value, Element $element)use($bindMatcher, $documentElement, $subComponent1, $subComponent2):void { + match($bindMatcher->numberOfInvocations()) { + 1 => self::assertEquals(["title", "Title 1!", $subComponent1], [$key, $value, $element]), + 2 => self::assertEquals(["title", "Title 2!", $subComponent2], [$key, $value, $element]), + 3 => self::assertEquals(["title", "Main title!", $documentElement], [$key, $value, $element]), + }; + }); + + $sut = new DocumentBinder($document); + $sut->setDependencies( + $elementBinder, + self::createMock(PlaceholderBinder::class), + self::createMock(TableBinder::class), + self::createMock(ListBinder::class), + self::createMock(ListElementCollection::class), + self::createMock(BindableCache::class), + ); + + $sut->bindKeyValue("title", "Title 1!", "#subcomponent-1"); + $sut->bindKeyValue("title", "Title 2!", "#subcomponent-2"); + $sut->bindKeyValue("title", "Main title!"); + } + + public function testBindData_stringContext():void { + $document = new HTMLDocument(HTMLPageContent::HTML_COMPONENT_WITH_ATTRIBUTE_NESTED); + $documentElement = $document->documentElement; + $subComponent1 = $document->querySelector("#subcomponent-1"); + $subComponent2 = $document->querySelector("#subcomponent-2"); + + $elementBinder = self::createMock(ElementBinder::class); + $bindMatcher = self::exactly(6); + $elementBinder->expects($bindMatcher) + ->method("bind") + ->willReturnCallback(function(string $key, string $value, Element $element)use($bindMatcher, $documentElement, $subComponent1, $subComponent2):void { + match($bindMatcher->numberOfInvocations()) { + 1 => self::assertEquals(["title", "Title 1!", $subComponent1], [$key, $value, $element]), + 2 => self::assertEquals(["number", "1", $subComponent1], [$key, $value, $element]), + 3 => self::assertEquals(["title", "Title 2!", $subComponent2], [$key, $value, $element]), + 4 => self::assertEquals(["number", "2", $subComponent2], [$key, $value, $element]), + 5 => self::assertEquals(["title", "Main title!", $documentElement], [$key, $value, $element]), + 6 => self::assertEquals(["number", "3", $documentElement], [$key, $value, $element]), + }; + }); + + $sut = new DocumentBinder($document); + $sut->setDependencies( + $elementBinder, + self::createMock(PlaceholderBinder::class), + self::createMock(TableBinder::class), + self::createMock(ListBinder::class), + self::createMock(ListElementCollection::class), + self::createMock(BindableCache::class), + ); + + $sut->bindData(["title" => "Title 1!", "number" => "1"], "#subcomponent-1"); + $sut->bindData(["title" => "Title 2!", "number" => "2"], "#subcomponent-2"); + $sut->bindData(["title" => "Main title!", "number" => "3"]); + } + + public function testBindList_stringContext():void { + $document = new HTMLDocument(HTMLPageContent::HTML_COMPONENT_WITH_ATTRIBUTE_NESTED); + $documentElement = $document->documentElement; + $subComponent1 = $document->querySelector("#subcomponent-1"); + $subComponent2 = $document->querySelector("#subcomponent-2"); + + $listBinder = self::createMock(ListBinder::class); + $bindMatcher = self::exactly(3); + $listBinder->expects($bindMatcher) + ->method("bindListData") + ->willReturnCallback(function(array $listData, Element|Document $context)use($bindMatcher, $documentElement, $subComponent1, $subComponent2):int { + match($bindMatcher->numberOfInvocations()) { + 1 => self::assertEquals([["List", "for", "component 2"], $subComponent2], [$listData, $context]), + 2 => self::assertEquals([["List", "for", "component 1"], $subComponent1], [$listData, $context]), + 3 => self::assertEquals([["List", "for", "main component"], $documentElement], [$listData, $context]), + }; + + return 0; + }); + + $sut = new DocumentBinder($document); + $sut->setDependencies( + self::createMock(ElementBinder::class), + self::createMock(PlaceholderBinder::class), + self::createMock(TableBinder::class), + $listBinder, + self::createMock(ListElementCollection::class), + self::createMock(BindableCache::class), + ); + + $sut->bindList(["List", "for", "component 2"], "#subcomponent-2"); + $sut->bindList(["List", "for", "component 1"], "#subcomponent-1"); + $sut->bindList(["List", "for", "main component"]); + } + + public function testBindValue_stringContext():void { + $document = new HTMLDocument(HTMLPageContent::HTML_COMPONENT_WITH_ATTRIBUTE_NESTED); + $documentElement = $document->documentElement; + $subComponent1 = $document->querySelector("#subcomponent-1"); + $subComponent2 = $document->querySelector("#subcomponent-2"); + + $elementBinder = self::createMock(ElementBinder::class); + $bindMatcher = self::exactly(3); + $elementBinder->expects($bindMatcher) + ->method("bind") + ->willReturnCallback(function(?string $key, string $value, Element $element)use($bindMatcher, $documentElement, $subComponent1, $subComponent2):void { + match($bindMatcher->numberOfInvocations()) { + 1 => self::assertEquals([null, "1", $subComponent1], [$key, $value, $element]), + 2 => self::assertEquals([null, "2", $subComponent2], [$key, $value, $element]), + 3 => self::assertEquals([null, "3", $documentElement], [$key, $value, $element]), + }; + }); + + $sut = new DocumentBinder($document); + $sut->setDependencies( + $elementBinder, + self::createMock(PlaceholderBinder::class), + self::createMock(TableBinder::class), + self::createMock(ListBinder::class), + self::createMock(ListElementCollection::class), + self::createMock(BindableCache::class), + ); + + $sut->bindValue("1", "#subcomponent-1"); + $sut->bindValue("2", "#subcomponent-2"); + $sut->bindValue("3"); + } + private function documentBinderDependencies(HTMLDocument $document, mixed...$otherObjectList):array { $htmlAttributeBinder = new HTMLAttributeBinder(); $htmlAttributeCollection = new HTMLAttributeCollection(); diff --git a/test/phpunit/TestHelper/HTMLPageContent.php b/test/phpunit/TestHelper/HTMLPageContent.php index c9601b0..6ee8104 100644 --- a/test/phpunit/TestHelper/HTMLPageContent.php +++ b/test/phpunit/TestHelper/HTMLPageContent.php @@ -587,6 +587,22 @@ class HTMLPageContent { HTML; + const HTML_COMPONENT_WITH_ATTRIBUTE_NESTED = << +

Hello, World!

+ + +

Component title

+ + +

Sub-component title

+
+ +

Sub-component title

+
+
+HTML; + const HTML_COMPONENT_NESTED_OUTER = <<