Skip to content

Commit 4de03b4

Browse files
authored
Support inverse boolean data-element bindings (#547)
* test: cover inverse boolean element presence for #466 * fix: support inverse boolean data-element bindings
1 parent d720cdc commit 4de03b4

2 files changed

Lines changed: 84 additions & 3 deletions

File tree

‎src/HTMLAttributeBinder.php‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,17 @@ private function markBoundElement(
120120
?string $key,
121121
mixed $value,
122122
):void {
123-
if($attribute->name === "data-element"
124-
&& $attribute->value === $key
125-
&& $value) {
123+
if($attribute->name !== "data-element") {
124+
return;
125+
}
126+
127+
$bindKey = $attribute->value;
128+
if(str_starts_with($bindKey, "!")) {
129+
$bindKey = substr($bindKey, 1);
130+
$value = !$value;
131+
}
132+
133+
if($bindKey === $key && $value) {
126134
$element->setAttribute("data-bound", "");
127135
}
128136
}

‎test/phpunit/DocumentBinderTest.php‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
use GT\DomTemplate\Test\TestHelper\Model\IteratorAggregate\Student\Module;
3737
use GT\DomTemplate\Test\TestHelper\Model\IteratorAggregate\Student\Name;
3838
use GT\DomTemplate\Test\TestHelper\Model\IteratorAggregate\Student\Student;
39+
use PHPUnit\Framework\Attributes\DataProvider;
3940
use PHPUnit\Framework\TestCase;
4041
use stdClass;
4142
use IteratorAggregate;
@@ -1526,6 +1527,78 @@ public function test_bindElementWithBindValue():void {
15261527
self::assertSame("There has been an error!", $errorDiv->textContent);
15271528
}
15281529

1530+
#[DataProvider("inverseElementPresenceProvider")]
1531+
public function testBindElement_inverseBoolean(
1532+
string $expression,
1533+
mixed $value,
1534+
bool $bind,
1535+
bool $retained,
1536+
):void {
1537+
$document = new HTMLDocument('<div id="target" data-element="' . $expression . '">Static content</div>');
1538+
$sut = new DocumentBinder($document);
1539+
$sut->setDependencies(...$this->documentBinderDependencies($document));
1540+
if($bind) {
1541+
$sut->bindKeyValue("isAdmin", $value);
1542+
}
1543+
$sut->cleanupDocument();
1544+
1545+
$target = $document->getElementById("target");
1546+
if(!$retained) {
1547+
self::assertNull($target);
1548+
return;
1549+
}
1550+
1551+
self::assertNotNull($target);
1552+
self::assertSame("Static content", $target->textContent);
1553+
self::assertFalse($target->hasAttribute("data-element"));
1554+
self::assertFalse($target->hasAttribute("data-bound"));
1555+
}
1556+
1557+
public static function inverseElementPresenceProvider():array {
1558+
$cases = [];
1559+
foreach(["isAdmin", "!isAdmin"] as $expression) {
1560+
$inverse = str_starts_with($expression, "!");
1561+
foreach([true, false, 1, 0, "admin", "", "0"] as $index => $value) {
1562+
$cases["$expression value $index"] = [$expression, $value, true, $inverse ? !$value : (bool)$value];
1563+
}
1564+
$cases["$expression unbound"] = [$expression, false, false, false];
1565+
$cases["$expression null"] = [$expression, null, true, false];
1566+
}
1567+
$cases["unrelated inverse key"] = ["!otherKey", false, true, false];
1568+
return $cases;
1569+
}
1570+
1571+
#[DataProvider("inverseAttributeElementProvider")]
1572+
public function testBindElement_inverseAttributeSurvivesCleanup(
1573+
string $modifier,
1574+
bool $value,
1575+
):void {
1576+
$isToken = str_contains($modifier, ":");
1577+
$property = $isToken ? "class" : "disabled";
1578+
$expression = $modifier . "isEnabled" . ($isToken ? " hidden" : "");
1579+
$document = new HTMLDocument('<button id="target" data-element data-bind:' . $property . '="' . $expression . '">Action</button>');
1580+
$sut = new DocumentBinder($document);
1581+
$sut->setDependencies(...$this->documentBinderDependencies($document));
1582+
$sut->bindKeyValue("isEnabled", $value);
1583+
$sut->cleanupDocument();
1584+
1585+
$target = $document->getElementById("target");
1586+
self::assertNotNull($target);
1587+
self::assertSame(!$value, $isToken ? $target->classList->contains("hidden") : $target->hasAttribute("disabled"));
1588+
self::assertFalse($target->hasAttribute("data-element"));
1589+
self::assertFalse($target->hasAttribute("data-bound"));
1590+
}
1591+
1592+
public static function inverseAttributeElementProvider():array {
1593+
$cases = [];
1594+
foreach(["?!", "!?", ":!", "!:"] as $modifier) {
1595+
foreach([true, false] as $value) {
1596+
$cases[$modifier . ($value ? " true" : " false")] = [$modifier, $value];
1597+
}
1598+
}
1599+
return $cases;
1600+
}
1601+
15291602
public function test_bindElementRemovesMultiple():void {
15301603
$document = new HTMLDocument(HTMLPageContent::HTML_ADMIN_PANEL);
15311604
$sut = new DocumentBinder($document);

0 commit comments

Comments
 (0)