Skip to content

Commit a0102e3

Browse files
committed
chore: tests and minor improvements.
1 parent 3200f28 commit a0102e3

11 files changed

Lines changed: 1659 additions & 1487 deletions

clover.xml

Lines changed: 1484 additions & 1474 deletions
Large diffs are not rendered by default.

src/Delegator/HTMLDocumentDelegator.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ class HTMLDocumentDelegator implements HTMLDocumentDelegatorInterface
6060
use DelegatorTrait;
6161
use ClassResolverTrait;
6262

63+
/** @var array<string, self> */
64+
private static array $instances = [];
65+
6366
public bool $formatOutput;
6467

6568
public function __construct(
@@ -72,6 +75,7 @@ public function __construct(
7275
if ($renderer === null) {
7376
$this->renderer = new HTMLGenerator();
7477
}
78+
self::$instances[spl_object_hash($this->delegated)] = $this;
7579
}
7680

7781
public function __toString(): string
@@ -84,6 +88,12 @@ public function setRenderer(TemplateGeneratorInterface $renderer): void
8488
$this->renderer = $renderer;
8589
}
8690

91+
public static function getInstance(HTMLDocument $document): self
92+
{
93+
$key = spl_object_hash($document);
94+
return self::$instances[$key] ?? new self($document);
95+
}
96+
8797
public static function createEmpty(): self
8898
{
8999
return new self(HTMLDocument::createEmpty());

src/Delegator/HTMLElementDelegator.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Html\Helper\Helper;
1212
use Html\Interface\HTMLElementDelegatorInterface;
1313
use Html\Interface\TemplateGeneratorInterface;
14+
use Html\Interface\TextDelegatorInterface;
1415
use Html\TemplateGenerator\HTMLGenerator;
1516
use Html\Trait\DelegatorTrait;
1617
// use Html\Trait\GlobalAttributesTrait;
@@ -137,7 +138,7 @@ public function appendChild(HTMLElementDelegatorInterface|TextDelegator $child):
137138
return $this;
138139
}
139140

140-
public function removeChild(HTMLElementDelegatorInterface|Text $child): static
141+
public function removeChild(HTMLElementDelegatorInterface|TextDelegatorInterface|Text $child): static
141142
{
142143
if (! \property_exists($child, 'ownerDocument')) {
143144
throw new Exception('The child element must be an instance of HTMLElementDelegatorInterface or Text.');
@@ -153,6 +154,10 @@ public function removeChild(HTMLElementDelegatorInterface|Text $child): static
153154
$this->delegated->removeChild($child->delegated);
154155
return $this;
155156
}
157+
if ($child instanceof TextDelegatorInterface) {
158+
$this->delegated->removeChild($child->delegated);
159+
return $this;
160+
}
156161
$this->delegated->removeChild($child);
157162
return $this;
158163
}
@@ -173,6 +178,12 @@ public function replaceChild(
173178
'The node element must belong to the same document as the parent element.'
174179
);
175180
}
181+
if ($child->getOwnerDocument() !== $this->getOwnerDocument()) {
182+
/** @todo the child could be imported here */
183+
throw new InvalidArgumentException(
184+
'The child element must belong to the same document as the parent element.'
185+
);
186+
}
176187
$this->delegated->replaceChild($node->delegated, $child->delegated);
177188
return $node;
178189
}
@@ -307,8 +318,8 @@ public static function parentOf(): array
307318
return static::$parentOf;
308319
}
309320

310-
public static function getOwnerDocument(): HTMLDocumentDelegator
321+
public function getOwnerDocument(): HTMLDocumentDelegator
311322
{
312-
return static::$ownerDocument;
323+
return HTMLDocumentDelegator::getInstance($this->delegated->ownerDocument);
313324
}
314325
}

src/Delegator/NodeDelegator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ public function getNode(): Node
3232
return $this->delegated;
3333
}
3434

35-
public static function getOwnerDocument(): HTMLDocumentDelegator
35+
public function getOwnerDocument(): HTMLDocumentDelegator
3636
{
37-
return static::$ownerDocument;
37+
return HTMLDocumentDelegator::getInstance($this->delegated->ownerDocument);
3838
}
3939
}

src/Delegator/TextDelegator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ public function getText(): Text
3232
return $this->delegated;
3333
}
3434

35-
public static function getOwnerDocument(): HTMLDocumentDelegator
35+
public function getOwnerDocument(): HTMLDocumentDelegator
3636
{
37-
return static::$ownerDocument;
37+
return HTMLDocumentDelegator::getInstance($this->delegated->ownerDocument);
3838
}
3939
}

src/Service/ComponentBuilder.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ private function buildDOM(HTMLDocumentDelegatorInterface $doc, array $nodeData,
5858

5959
// Append to parent node if set - or document if root node
6060
if ($parent === null) {
61-
$importedNode = $doc->importNode($element->delegated, true);
62-
$doc->appendChild($importedNode);
61+
$doc->appendChild($element);
6362
} else {
6463
// exit('there');
6564
$parent->appendChild($element);

tests/Delegator/HTMLDocumentDelegatorTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,43 @@
373373
->toBe('Updated Paragraph Text');
374374
});
375375

376+
test('querySelectorAll returns null for non-existent selector', function () {
377+
$html = '<!DOCTYPE html><html><head><title>Test</title></head><body><div></div></body></html>';
378+
$delegator = HTMLDocumentDelegator::createFromString($html);
379+
380+
$elements = $delegator->querySelectorAll('.non-existent-class');
381+
expect($elements)->toBeInstanceOf(NodeListDelegator::class);
382+
expect(count($elements))->toBe(0);
383+
});
384+
385+
test('constructor with invalid renderer', function () {
386+
$mockRenderer = new class implements \Html\Interface\TemplateGeneratorInterface {
387+
public function getExtension(): string {
388+
return 'html';
389+
}
390+
public function getNamePattern(): string {
391+
return '*.html';
392+
}
393+
public function canRenderElements(): bool {
394+
return false;
395+
}
396+
public function canRenderDocuments(): bool {
397+
return true;
398+
}
399+
public function isTemplated(): bool {
400+
return false;
401+
}
402+
public function render($elementOrDocument): ?string {
403+
return '';
404+
}
405+
};
406+
407+
$this->expectException(InvalidArgumentException::class);
408+
$this->expectExceptionMessage('The given renderer cannot render elements.');
409+
410+
new HTMLDocumentDelegator($this->document, $mockRenderer);
411+
});
412+
376413
test('create text node', function () {
377414
$textNode = $this->delegator->createTextNode('Hello World');
378415
expect($textNode)

tests/Delegator/HTMLElementDelegatorTest.php

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
use Html\Enum\TargetEnum;
1212
use Html\TemplateGenerator\HTMLGenerator;
1313

14-
// uses(\Html\Trait\GlobalAttributesTrait::class);
15-
1614
beforeEach(function () {
1715
$this->document = HTMLDocumentDelegator::createEmpty();
1816
$this->delegator = Anchor::create($this->document);
@@ -401,3 +399,88 @@
401399
expect($this->delegator->getOwnerDocument())
402400
->toBe($this->document);
403401
});
402+
403+
test('constructor with invalid renderer', function () {
404+
$mockRenderer = new class implements \Html\Interface\TemplateGeneratorInterface {
405+
public function getExtension(): string {
406+
return 'html';
407+
}
408+
public function getNamePattern(): string {
409+
return '*.html';
410+
}
411+
public function canRenderElements(): bool {
412+
return false;
413+
}
414+
public function canRenderDocuments(): bool {
415+
return true;
416+
}
417+
public function isTemplated(): bool {
418+
return false;
419+
}
420+
public function render($elementOrDocument): ?string {
421+
return '';
422+
}
423+
};
424+
425+
$element = $this->document->createElement('div');
426+
427+
$this->expectException(InvalidArgumentException::class);
428+
$this->expectExceptionMessage('The given renderer cannot render elements.');
429+
430+
new HTMLElementDelegator($element->delegated, $mockRenderer);
431+
});
432+
433+
test('set with union type enum handling', function () {
434+
// This test should trigger the union type enum handling in __set
435+
// We need a property that has a union type with BackedEnum
436+
$element = Body::create($this->document);
437+
438+
// Try to set a property that doesn't exist to trigger the union type handling
439+
$element->nonExistentProperty = 'test';
440+
expect($element->getAttribute('nonexistentproperty'))
441+
->toBe('test');
442+
});
443+
444+
test('append child with different owner document throws exception', function () {
445+
$otherDocument = HTMLDocumentDelegator::createEmpty();
446+
$child = Anchor::create($otherDocument);
447+
448+
$this->expectException(InvalidArgumentException::class);
449+
$this->expectExceptionMessage('The child element must belong to the same document as the parent element.');
450+
451+
$this->delegator->appendChild($child);
452+
});
453+
454+
test('remove child with different owner document throws exception', function () {
455+
$otherDocument = HTMLDocumentDelegator::createEmpty();
456+
$child = Anchor::create($otherDocument);
457+
458+
$this->expectException(InvalidArgumentException::class);
459+
$this->expectExceptionMessage('The child element must belong to the same document as the parent element.');
460+
461+
$this->delegator->removeChild($child);
462+
});
463+
464+
test('remove child with DOM Text', function () {
465+
$textNode = $this->document->createTextNode('test text');
466+
$element = $this->document->createElement('div');
467+
$element->appendChild($textNode);
468+
469+
expect($element->childNodes->length)->toBe(1);
470+
471+
$element->removeChild($textNode);
472+
expect($element->childNodes->length)->toBe(0);
473+
});
474+
475+
test('replace child with different owner document throws exception', function () {
476+
$otherDocument = HTMLDocumentDelegator::createEmpty();
477+
$child1 = Anchor::create($this->document);
478+
$child2 = Anchor::create($otherDocument);
479+
480+
$this->delegator->appendChild($child1);
481+
482+
$this->expectException(InvalidArgumentException::class);
483+
$this->expectExceptionMessage('The node element must belong to the same document as the parent element.');
484+
485+
$this->delegator->replaceChild($child2, $child1);
486+
});

tests/Delegator/NodeDelegatorTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,8 @@
109109
expect($anchor->contains($other->delegated))
110110
->toBeTrue();
111111
});
112+
113+
test('get owner document', function () {
114+
expect($this->delegator->getOwnerDocument())
115+
->toBe($this->document);
116+
});

tests/Delegator/NodeListDelegatorTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,20 @@
105105
expect($delegator->getNodeList())
106106
->toBe($nodeList);
107107
});
108+
109+
test('get invalid property', function () {
110+
$this->expectException(InvalidArgumentException::class);
111+
test()->delegator->nonExistentProperty;
112+
});
113+
114+
test('call invalid method', function () {
115+
$this->expectException(BadMethodCallException::class);
116+
test()->delegator->nonExistentMethod();
117+
});
118+
119+
test('item returns null for out of bounds', function () {
120+
$document = HTMLDocumentDelegator::createEmpty();
121+
$delegator = new NodeListDelegator($document->childNodes);
122+
expect($delegator->item(0))
123+
->toBeNull();
124+
});

0 commit comments

Comments
 (0)