simplification, cleanup, and tests
This commit is contained in:
parent
12afd5e0be
commit
82debbdb11
36 changed files with 441 additions and 344 deletions
|
@ -3,10 +3,31 @@
|
|||
namespace ByJoby\HTML;
|
||||
|
||||
use Stringable;
|
||||
use Traversable;
|
||||
|
||||
interface ContainerInterface extends Stringable
|
||||
{
|
||||
/** @return array<int,NodeInterface> */
|
||||
public function children(): array;
|
||||
|
||||
public function addChild(
|
||||
NodeInterface|Stringable|string $child,
|
||||
bool $prepend = false,
|
||||
bool $skip_sanitize = false
|
||||
): static;
|
||||
|
||||
public function removeChild(
|
||||
NodeInterface|Stringable|string $child
|
||||
): static;
|
||||
|
||||
public function addChildBefore(
|
||||
NodeInterface|Stringable|string $new_child,
|
||||
NodeInterface|Stringable|string $before_child,
|
||||
bool $skip_sanitize = false
|
||||
): static;
|
||||
|
||||
public function addChildAfter(
|
||||
NodeInterface|Stringable|string $new_child,
|
||||
NodeInterface|Stringable|string $after_child,
|
||||
bool $skip_sanitize = false
|
||||
): static;
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace ByJoby\HTML;
|
||||
|
||||
use Stringable;
|
||||
|
||||
interface ContainerMutableInterface extends ContainerInterface
|
||||
{
|
||||
public function addChild(
|
||||
NodeInterface|Stringable|string $child,
|
||||
bool $prepend = false,
|
||||
bool $skip_sanitize = false
|
||||
): static;
|
||||
|
||||
public function removeChild(
|
||||
NodeInterface|Stringable|string $child
|
||||
): static;
|
||||
|
||||
public function addChildBefore(
|
||||
NodeInterface|Stringable|string $new_child,
|
||||
NodeInterface|Stringable|string $before_child,
|
||||
bool $skip_sanitize = false
|
||||
): static;
|
||||
|
||||
public function addChildAfter(
|
||||
NodeInterface|Stringable|string $new_child,
|
||||
NodeInterface|Stringable|string $after_child,
|
||||
bool $skip_sanitize = false
|
||||
): static;
|
||||
}
|
|
@ -25,11 +25,6 @@ class TitleTag implements TitleTagInterface
|
|||
return $this->title;
|
||||
}
|
||||
|
||||
public function detach(): static
|
||||
{
|
||||
throw new Exception('Not allowed to detach TitleTag');
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '<title>' . $this->title() . '</title>';
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace ByJoby\HTML\Containers;
|
||||
|
||||
use ByJoby\HTML\NodeInterface;
|
||||
use ByJoby\HTML\Traits\ContainerMutableTrait;
|
||||
use ByJoby\HTML\Traits\ContainerTrait;
|
||||
use Stringable;
|
||||
use Traversable;
|
||||
|
@ -11,7 +10,6 @@ use Traversable;
|
|||
class Fragment implements FragmentInterface
|
||||
{
|
||||
use ContainerTrait;
|
||||
use ContainerMutableTrait;
|
||||
|
||||
/**
|
||||
* @param null|array<mixed,string|Stringable|NodeInterface>|Traversable<mixed,string|Stringable|NodeInterface>|null $children
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
namespace ByJoby\HTML\Containers;
|
||||
|
||||
use ByJoby\HTML\ContainerInterface;
|
||||
use ByJoby\HTML\ContainerMutableInterface;
|
||||
|
||||
interface FragmentInterface extends DocumentInterface, ContainerMutableInterface
|
||||
interface FragmentInterface extends DocumentInterface, ContainerInterface
|
||||
{
|
||||
}
|
||||
|
|
|
@ -2,17 +2,18 @@
|
|||
|
||||
namespace ByJoby\HTML\Containers;
|
||||
|
||||
use ByJoby\HTML\Containers\DocumentTags\BodyTag;
|
||||
use ByJoby\HTML\Containers\DocumentTags\BodyTagInterface;
|
||||
use ByJoby\HTML\Containers\DocumentTags\Doctype;
|
||||
use ByJoby\HTML\Containers\DocumentTags\DoctypeInterface;
|
||||
use ByJoby\HTML\Containers\DocumentTags\HeadTag;
|
||||
use ByJoby\HTML\Containers\DocumentTags\HeadTagInterface;
|
||||
use ByJoby\HTML\Containers\DocumentTags\HtmlTag;
|
||||
use ByJoby\HTML\Containers\DocumentTags\HtmlTagInterface;
|
||||
use ByJoby\HTML\Traits\ContainerTrait;
|
||||
|
||||
class GenericHtmlDocument implements HtmlDocumentInterface
|
||||
{
|
||||
use ContainerTrait;
|
||||
|
||||
/** @var DoctypeInterface */
|
||||
protected $doctype;
|
||||
/** @var HtmlTagInterface */
|
||||
|
@ -20,8 +21,10 @@ class GenericHtmlDocument implements HtmlDocumentInterface
|
|||
|
||||
public function __construct()
|
||||
{
|
||||
$this->doctype = (new Doctype)->setDocument($this);
|
||||
$this->html = (new HtmlTag)->setDocument($this);
|
||||
$this->doctype = (new Doctype);
|
||||
$this->html = (new HtmlTag);
|
||||
$this->addChild($this->doctype);
|
||||
$this->addChild($this->html);
|
||||
}
|
||||
|
||||
public function doctype(): DoctypeInterface
|
||||
|
|
|
@ -12,12 +12,12 @@ use Traversable;
|
|||
/**
|
||||
* Holds and validates a set of HTML attribute name/value pairs for use in tags.
|
||||
*
|
||||
* @implements ArrayAccess<string,null|string|Stringable>
|
||||
* @implements IteratorAggregate<string,null|string|Stringable>
|
||||
* @implements ArrayAccess<string,bool|string|Stringable>
|
||||
* @implements IteratorAggregate<string,bool|string|Stringable>
|
||||
*/
|
||||
class Attributes implements IteratorAggregate, ArrayAccess
|
||||
{
|
||||
/** @var array<string,null|string|Stringable> */
|
||||
/** @var array<string,bool|string|Stringable> */
|
||||
protected $array = [];
|
||||
/** @var bool */
|
||||
protected $sorted = true;
|
||||
|
@ -25,7 +25,7 @@ class Attributes implements IteratorAggregate, ArrayAccess
|
|||
protected $disallowed = [];
|
||||
|
||||
/**
|
||||
* @param null|array<string,null|string|Stringable> $array
|
||||
* @param null|array<string,bool|string|Stringable> $array
|
||||
* @param array<mixed,string> $disallowed
|
||||
* @return void
|
||||
*/
|
||||
|
@ -59,6 +59,13 @@ class Attributes implements IteratorAggregate, ArrayAccess
|
|||
$this->array[$offset] = $value;
|
||||
}
|
||||
|
||||
public function string(string $offset): null|string
|
||||
{
|
||||
$value = $this->offsetGet($offset);
|
||||
if (is_string($value)) return $value;
|
||||
else return null;
|
||||
}
|
||||
|
||||
function offsetUnset(mixed $offset): void
|
||||
{
|
||||
$offset = static::sanitizeOffset($offset);
|
||||
|
@ -66,7 +73,7 @@ class Attributes implements IteratorAggregate, ArrayAccess
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array<string,null|string|Stringable>
|
||||
* @return array<string,bool|string|Stringable>
|
||||
*/
|
||||
function getArray(): array
|
||||
{
|
||||
|
|
|
@ -11,12 +11,13 @@ class BaseTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function href(): null|string
|
||||
{
|
||||
return $this->attributes()['href'];
|
||||
return $this->attributes()->string('href');
|
||||
}
|
||||
|
||||
public function setHref(null|string $href): static
|
||||
{
|
||||
$this->attributes()['href'] = $href;
|
||||
if (!$href) $this->attributes()['href'] = false;
|
||||
else $this->attributes()['href'] = $href;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -28,12 +29,13 @@ class BaseTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function target(): null|string
|
||||
{
|
||||
return $this->attributes()['target'];
|
||||
return $this->attributes()->string('target');
|
||||
}
|
||||
|
||||
public function setTarget(null|string $target): static
|
||||
{
|
||||
$this->attributes()['target'] = $target;
|
||||
if (!$target) $this->attributes()['target'] = false;
|
||||
else $this->attributes()['target'] = $target;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,13 @@ class LinkTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function rel(): null|string
|
||||
{
|
||||
return $this->attributes()['rel'];
|
||||
return $this->attributes()->string('rel');
|
||||
}
|
||||
|
||||
public function setRel(null|string $rel): static
|
||||
{
|
||||
$this->attributes()['rel'] = $rel;
|
||||
if (!$rel) $this->attributes()['rel'] = false;
|
||||
else $this->attributes()['rel'] = $rel;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -28,12 +29,13 @@ class LinkTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function as(): null|string
|
||||
{
|
||||
return $this->attributes()['as'];
|
||||
return $this->attributes()->string('as');
|
||||
}
|
||||
|
||||
public function setAs(null|string $as): static
|
||||
{
|
||||
$this->attributes()['as'] = $as;
|
||||
if (!$as) $this->attributes()['as'] = false;
|
||||
else $this->attributes()['as'] = $as;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -45,12 +47,13 @@ class LinkTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function crossorigin(): null|string
|
||||
{
|
||||
return $this->attributes()['crossorigin'];
|
||||
return $this->attributes()->string('crossorigin');
|
||||
}
|
||||
|
||||
public function setCrossorigin(null|string $crossorigin): static
|
||||
{
|
||||
$this->attributes()['crossorigin'] = $crossorigin;
|
||||
if (!$crossorigin) $this->attributes()['crossorigin'] = false;
|
||||
else $this->attributes()['crossorigin'] = $crossorigin;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -62,12 +65,13 @@ class LinkTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function href(): null|string
|
||||
{
|
||||
return $this->attributes()['href'];
|
||||
return $this->attributes()->string('href');
|
||||
}
|
||||
|
||||
public function setHref(null|string $href): static
|
||||
{
|
||||
$this->attributes()['href'] = $href;
|
||||
if (!$href) $this->attributes()['href'] = false;
|
||||
else $this->attributes()['href'] = $href;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -79,12 +83,13 @@ class LinkTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function hreflang(): null|string
|
||||
{
|
||||
return $this->attributes()['hreflang'];
|
||||
return $this->attributes()->string('hreflang');
|
||||
}
|
||||
|
||||
public function setHreflang(null|string $hreflang): static
|
||||
{
|
||||
$this->attributes()['hreflang'] = $hreflang;
|
||||
if (!$hreflang) $this->attributes()['hreflang'] = false;
|
||||
else $this->attributes()['hreflang'] = $hreflang;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -96,12 +101,13 @@ class LinkTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function imagesizes(): null|string
|
||||
{
|
||||
return $this->attributes()['imagesizes'];
|
||||
return $this->attributes()->string('imagesizes');
|
||||
}
|
||||
|
||||
public function setImagesizes(null|string $imagesizes): static
|
||||
{
|
||||
$this->attributes()['imagesizes'] = $imagesizes;
|
||||
if (!$imagesizes) $this->attributes()['imagesizes'] = false;
|
||||
else $this->attributes()['imagesizes'] = $imagesizes;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -113,12 +119,13 @@ class LinkTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function imagesrcset(): null|string
|
||||
{
|
||||
return $this->attributes()['imagesrcset'];
|
||||
return $this->attributes()->string('imagesrcset');
|
||||
}
|
||||
|
||||
public function setImagesrcset(null|string $imagesrcset): static
|
||||
{
|
||||
$this->attributes()['imagesrcset'] = $imagesrcset;
|
||||
if (!$imagesrcset) $this->attributes()['imagesrcset'] = false;
|
||||
else $this->attributes()['imagesrcset'] = $imagesrcset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -130,12 +137,13 @@ class LinkTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function integrity(): null|string
|
||||
{
|
||||
return $this->attributes()['integrity'];
|
||||
return $this->attributes()->string('integrity');
|
||||
}
|
||||
|
||||
public function setIntegrity(null|string $integrity): static
|
||||
{
|
||||
$this->attributes()['integrity'] = $integrity;
|
||||
if (!$integrity) $this->attributes()['integrity'] = false;
|
||||
else $this->attributes()['integrity'] = $integrity;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -147,12 +155,13 @@ class LinkTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function media(): null|string
|
||||
{
|
||||
return $this->attributes()['media'];
|
||||
return $this->attributes()->string('media');
|
||||
}
|
||||
|
||||
public function setMedia(null|string $media): static
|
||||
{
|
||||
$this->attributes()['media'] = $media;
|
||||
if (!$media) $this->attributes()['media'] = false;
|
||||
else $this->attributes()['media'] = $media;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -164,12 +173,13 @@ class LinkTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function referrerpolicy(): null|string
|
||||
{
|
||||
return $this->attributes()['referrerpolicy'];
|
||||
return $this->attributes()->string('referrerpolicy');
|
||||
}
|
||||
|
||||
public function setReferrerpolicy(null|string $referrerpolicy): static
|
||||
{
|
||||
$this->attributes()['referrerpolicy'] = $referrerpolicy;
|
||||
if (!$referrerpolicy) $this->attributes()['referrerpolicy'] = false;
|
||||
else $this->attributes()['referrerpolicy'] = $referrerpolicy;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -181,12 +191,13 @@ class LinkTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function type(): null|string
|
||||
{
|
||||
return $this->attributes()['type'];
|
||||
return $this->attributes()->string('type');
|
||||
}
|
||||
|
||||
public function setType(null|string $type): static
|
||||
{
|
||||
$this->attributes()['type'] = $type;
|
||||
if (!$type) $this->attributes()['type'] = false;
|
||||
else $this->attributes()['type'] = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,13 @@ class MetaTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function name(): null|string
|
||||
{
|
||||
return $this->attributes()['name'];
|
||||
return $this->attributes()->string('name');
|
||||
}
|
||||
|
||||
public function setName(null|string $name): static
|
||||
{
|
||||
$this->attributes()['name'] = $name;
|
||||
if (!$name) $this->attributes()['name'] = false;
|
||||
else $this->attributes()['name'] = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -28,12 +29,13 @@ class MetaTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function content(): null|string
|
||||
{
|
||||
return $this->attributes()['content'];
|
||||
return $this->attributes()->string('content');
|
||||
}
|
||||
|
||||
public function setContent(null|string $content): static
|
||||
{
|
||||
$this->attributes()['content'] = $content;
|
||||
if (!$content) $this->attributes()['content'] = false;
|
||||
else $this->attributes()['content'] = $content;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -45,12 +47,13 @@ class MetaTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function httpEquiv(): null|string
|
||||
{
|
||||
return $this->attributes()['http-equiv'];
|
||||
return $this->attributes()->string('http-equiv');
|
||||
}
|
||||
|
||||
public function setHttpEquiv(null|string $http_equiv): static
|
||||
{
|
||||
$this->attributes()['http-equiv'] = $http_equiv;
|
||||
if (!$http_equiv) $this->attributes()['http-equiv'] = false;
|
||||
else $this->attributes()['http-equiv'] = $http_equiv;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -62,12 +65,13 @@ class MetaTag extends AbstractTag implements MetadataContent
|
|||
|
||||
public function charset(): null|string
|
||||
{
|
||||
return $this->attributes()['charset'];
|
||||
return $this->attributes()->string('charset');
|
||||
}
|
||||
|
||||
public function setCharset(null|string $charset): static
|
||||
{
|
||||
$this->attributes()['charset'] = $charset;
|
||||
if (!$charset) $this->attributes()['charset'] = false;
|
||||
else $this->attributes()['charset'] = $charset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,36 +13,35 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
|||
|
||||
public function setAsync(bool $async): static
|
||||
{
|
||||
if ($async) $this->attributes()['async'] = null;
|
||||
else unset($this->attributes()['async']);
|
||||
$this->attributes()['async'] = $async;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function async(): bool
|
||||
{
|
||||
return isset($this->attributes()['async']);
|
||||
return !!$this->attributes()['async'];
|
||||
}
|
||||
|
||||
public function setDefer(bool $defer): static
|
||||
{
|
||||
if ($defer) $this->attributes()['defer'] = null;
|
||||
else unset($this->attributes()['defer']);
|
||||
$this->attributes()['defer'] = $defer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function defer(): bool
|
||||
{
|
||||
return isset($this->attributes()['defer']);
|
||||
return !!$this->attributes()['defer'];
|
||||
}
|
||||
|
||||
public function crossorigin(): null|string
|
||||
{
|
||||
return $this->attributes()['crossorigin'];
|
||||
return $this->attributes()->string('crossorigin');
|
||||
}
|
||||
|
||||
public function setCrossorigin(null|string $crossorigin): static
|
||||
{
|
||||
$this->attributes()['crossorigin'] = $crossorigin;
|
||||
if (!$crossorigin) $this->attributes()['crossorigin'] = false;
|
||||
else $this->attributes()['crossorigin'] = $crossorigin;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -54,12 +53,13 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
|||
|
||||
public function integrity(): null|string
|
||||
{
|
||||
return $this->attributes()['integrity'];
|
||||
return $this->attributes()->string('integrity');
|
||||
}
|
||||
|
||||
public function setIntegrity(null|string $integrity): static
|
||||
{
|
||||
$this->attributes()['integrity'] = $integrity;
|
||||
if (!$integrity) $this->attributes()['integrity'] = false;
|
||||
else $this->attributes()['integrity'] = $integrity;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -69,31 +69,26 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function nomodule(): null|string
|
||||
{
|
||||
return $this->attributes()['nomodule'];
|
||||
}
|
||||
|
||||
public function setNomodule(null|string $nomodule): static
|
||||
public function setNomodule(bool $nomodule): static
|
||||
{
|
||||
$this->attributes()['nomodule'] = $nomodule;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function unsetNomodule(): static
|
||||
public function nomodule(): bool
|
||||
{
|
||||
unset($this->attributes()['nomodule']);
|
||||
return $this;
|
||||
return !!$this->attributes()['nomodule'];
|
||||
}
|
||||
|
||||
public function nonce(): null|string
|
||||
{
|
||||
return $this->attributes()['nonce'];
|
||||
return $this->attributes()->string('nonce');
|
||||
}
|
||||
|
||||
public function setNonce(null|string $nonce): static
|
||||
{
|
||||
$this->attributes()['nonce'] = $nonce;
|
||||
if (!$nonce) $this->attributes()['nonce'] = false;
|
||||
else $this->attributes()['nonce'] = $nonce;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -105,12 +100,13 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
|||
|
||||
public function referrerpolicy(): null|string
|
||||
{
|
||||
return $this->attributes()['referrerpolicy'];
|
||||
return $this->attributes()->string('referrerpolicy');
|
||||
}
|
||||
|
||||
public function setReferrerpolicy(null|string $referrerpolicy): static
|
||||
{
|
||||
$this->attributes()['referrerpolicy'] = $referrerpolicy;
|
||||
if (!$referrerpolicy) $this->attributes()['referrerpolicy'] = false;
|
||||
else $this->attributes()['referrerpolicy'] = $referrerpolicy;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -122,12 +118,13 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
|||
|
||||
public function src(): null|string
|
||||
{
|
||||
return $this->attributes()['src'];
|
||||
return $this->attributes()->string('src');
|
||||
}
|
||||
|
||||
public function setSrc(null|string $src): static
|
||||
{
|
||||
$this->attributes()['src'] = $src;
|
||||
if (!$src) $this->attributes()['src'] = false;
|
||||
else $this->attributes()['src'] = $src;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -139,12 +136,13 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
|||
|
||||
public function type(): null|string
|
||||
{
|
||||
return $this->attributes()['type'];
|
||||
return $this->attributes()->string('type');
|
||||
}
|
||||
|
||||
public function setType(null|string $type): static
|
||||
{
|
||||
$this->attributes()['type'] = $type;
|
||||
if (!$type) $this->attributes()['type'] = false;
|
||||
else $this->attributes()['type'] = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,13 @@ class StyleTag extends AbstractContentTag implements MetadataContent
|
|||
|
||||
public function media(): null|string
|
||||
{
|
||||
return $this->attributes()['media'];
|
||||
return $this->attributes()->string('media');
|
||||
}
|
||||
|
||||
public function setMedia(null|string $media): static
|
||||
{
|
||||
$this->attributes()['media'] = $media;
|
||||
if (!$media) $this->attributes()['media'] = false;
|
||||
else $this->attributes()['media'] = $media;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -28,12 +29,13 @@ class StyleTag extends AbstractContentTag implements MetadataContent
|
|||
|
||||
public function nonce(): null|string
|
||||
{
|
||||
return $this->attributes()['nonce'];
|
||||
return $this->attributes()->string('nonce');
|
||||
}
|
||||
|
||||
public function setNonce(null|string $nonce): static
|
||||
{
|
||||
$this->attributes()['nonce'] = $nonce;
|
||||
if (!$nonce) $this->attributes()['nonce'] = false;
|
||||
else $this->attributes()['nonce'] = $nonce;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,21 +3,27 @@
|
|||
namespace ByJoby\HTML;
|
||||
|
||||
use ByJoby\HTML\Containers\DocumentInterface;
|
||||
use ByJoby\HTML\Tags\TagInterface;
|
||||
use Stringable;
|
||||
|
||||
interface NodeInterface extends Stringable
|
||||
{
|
||||
public function parent(): null|NodeInterface;
|
||||
public function parent(): null|ContainerInterface;
|
||||
|
||||
public function setParent(
|
||||
null|NodeInterface $parent
|
||||
null|ContainerInterface $parent
|
||||
): static;
|
||||
|
||||
public function document(): null|DocumentInterface;
|
||||
public function parentTag(): null|TagInterface;
|
||||
|
||||
public function setDocument(
|
||||
null|DocumentInterface $parent
|
||||
): static;
|
||||
public function parentDocument(): null|DocumentInterface;
|
||||
|
||||
public function detach(): static;
|
||||
/**
|
||||
* @template T of NodeInterface
|
||||
* @param class-string<T> $class
|
||||
* @return null|T
|
||||
*/
|
||||
public function parentOfType(string $class): mixed;
|
||||
|
||||
public function detachCopy(): static;
|
||||
}
|
||||
|
|
|
@ -13,14 +13,25 @@ class Comment implements CommentInterface
|
|||
{
|
||||
}
|
||||
|
||||
public function value(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue(string|Stringable $value): static
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf(
|
||||
'<!-- %s -->',
|
||||
str_replace(
|
||||
'--', // regular hyphens
|
||||
'‑‑', // non-breaking hyphens
|
||||
$this->value
|
||||
'‑‑', // non-breaking hyphens, so they can't end the comment
|
||||
$this->value()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,4 +8,6 @@ use Stringable;
|
|||
interface CommentInterface extends NodeInterface
|
||||
{
|
||||
public function __construct(Stringable|string $value);
|
||||
public function value(): string;
|
||||
public function setValue(string|Stringable $value): static;
|
||||
}
|
||||
|
|
|
@ -13,8 +13,19 @@ class Text implements TextInterface
|
|||
{
|
||||
}
|
||||
|
||||
public function value(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue(string|Stringable $value): static
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return htmlentities(strip_tags($this->value));
|
||||
return htmlentities(strip_tags($this->value()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,4 +8,6 @@ use Stringable;
|
|||
interface TextInterface extends NodeInterface
|
||||
{
|
||||
public function __construct(Stringable|string $value);
|
||||
public function value(): string;
|
||||
public function setValue(string|Stringable $value): static;
|
||||
}
|
||||
|
|
|
@ -13,8 +13,19 @@ class UnsanitizedText implements TextInterface
|
|||
{
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
public function value(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue(string|Stringable $value): static
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->value();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace ByJoby\HTML\Tags;
|
||||
|
||||
use ByJoby\HTML\Traits\ContainerMutableTrait;
|
||||
use ByJoby\HTML\Traits\ContainerTrait;
|
||||
use ByJoby\HTML\Traits\TagTrait;
|
||||
use ByJoby\HTML\Traits\NodeTrait;
|
||||
|
@ -10,7 +9,7 @@ use ByJoby\HTML\Traits\NodeTrait;
|
|||
abstract class AbstractContainerTag extends AbstractTag implements ContainerTagInterface
|
||||
{
|
||||
use NodeTrait, TagTrait;
|
||||
use ContainerTrait, ContainerMutableTrait;
|
||||
use ContainerTrait;
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
|
|
7
src/Tags/AbstractGroupedTag.php
Normal file
7
src/Tags/AbstractGroupedTag.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace ByJoby\HTML\Tags;
|
||||
|
||||
abstract class AbstractGroupedTag extends AbstractContainerTag
|
||||
{
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace ByJoby\HTML\Tags;
|
||||
|
||||
use ByJoby\HTML\ContainerMutableInterface;
|
||||
use ByJoby\HTML\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Container Tags are HTML tags that are capable of holding a collection of
|
||||
|
@ -12,6 +12,6 @@ use ByJoby\HTML\ContainerMutableInterface;
|
|||
*
|
||||
* @package ByJoby\HTML\Tags
|
||||
*/
|
||||
interface ContainerTagInterface extends TagInterface, ContainerMutableInterface
|
||||
interface ContainerTagInterface extends TagInterface, ContainerInterface
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace ByJoby\HTML\Traits;
|
||||
|
||||
use ByJoby\HTML\Containers\DocumentInterface;
|
||||
use ByJoby\HTML\NodeInterface;
|
||||
use ByJoby\HTML\Nodes\Text;
|
||||
use ByJoby\HTML\Nodes\UnsanitizedText;
|
||||
use Exception;
|
||||
use Stringable;
|
||||
|
||||
trait ContainerMutableTrait
|
||||
{
|
||||
use ContainerTrait;
|
||||
|
||||
public function addChild(
|
||||
NodeInterface|Stringable|string $child,
|
||||
bool $prepend = false,
|
||||
bool $skip_sanitize = false
|
||||
): static {
|
||||
$child = $this->prepareChildToAdd($child, $skip_sanitize);
|
||||
if ($prepend) array_unshift($this->children, $child);
|
||||
else $this->children[] = $child;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeChild(
|
||||
NodeInterface|Stringable|string $child
|
||||
): static {
|
||||
$this->children = array_filter(
|
||||
$this->children,
|
||||
function (NodeInterface $e) use ($child) {
|
||||
if (is_object($child)) return $e !== $child;
|
||||
else return $e != $child;
|
||||
}
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addChildBefore(
|
||||
NodeInterface|Stringable|string $new_child,
|
||||
NodeInterface|Stringable|string $before_child,
|
||||
bool $skip_sanitize = false
|
||||
): static {
|
||||
$i = $this->indexOfChild($before_child);
|
||||
if ($i === null) {
|
||||
throw new Exception('Reference child not found in this container');
|
||||
}
|
||||
$new_child = $this->prepareChildToAdd($new_child, $skip_sanitize);
|
||||
array_splice($this->children, $i, 0, [$new_child]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addChildAfter(
|
||||
NodeInterface|Stringable|string $new_child,
|
||||
NodeInterface|Stringable|string $after_child,
|
||||
bool $skip_sanitize = false
|
||||
): static {
|
||||
$i = $this->indexOfChild($after_child);
|
||||
if ($i === null) {
|
||||
throw new Exception('Reference child not found in this container');
|
||||
}
|
||||
$new_child = $this->prepareChildToAdd($new_child, $skip_sanitize);
|
||||
array_splice($this->children, $i + 1, 0, [$new_child]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function prepareChildToAdd(NodeInterface|Stringable|string $child, bool $skip_sanitize): NodeInterface
|
||||
{
|
||||
if (!($child instanceof NodeInterface)) {
|
||||
if ($skip_sanitize) $child = new UnsanitizedText($child);
|
||||
else $child = new Text($child);
|
||||
}
|
||||
if ($this instanceof NodeInterface) {
|
||||
if ($child->parent() || $child->document()) {
|
||||
$child->detach();
|
||||
}
|
||||
$child->setParent($this);
|
||||
$child->setDocument($this->document());
|
||||
}
|
||||
if ($this instanceof DocumentInterface) {
|
||||
if ($child->parent() || $child->document()) {
|
||||
$child->detach();
|
||||
}
|
||||
$child->setDocument($this);
|
||||
}
|
||||
return $child;
|
||||
}
|
||||
|
||||
protected function indexOfChild(NodeInterface|Stringable|string $child): null|int
|
||||
{
|
||||
if ($child instanceof NodeInterface) {
|
||||
foreach ($this->children() as $i => $v) {
|
||||
if ($v === $child) return $i;
|
||||
}
|
||||
} else {
|
||||
foreach ($this->children() as $i => $v) {
|
||||
if ($v == $child) return $i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,10 @@
|
|||
namespace ByJoby\HTML\Traits;
|
||||
|
||||
use ByJoby\HTML\NodeInterface;
|
||||
use ByJoby\HTML\Nodes\Text;
|
||||
use ByJoby\HTML\Nodes\UnsanitizedText;
|
||||
use Exception;
|
||||
use Stringable;
|
||||
|
||||
trait ContainerTrait
|
||||
{
|
||||
|
@ -14,4 +18,89 @@ trait ContainerTrait
|
|||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function addChild(
|
||||
NodeInterface|Stringable|string $child,
|
||||
bool $prepend = false,
|
||||
bool $skip_sanitize = false
|
||||
): static {
|
||||
$child = $this->prepareChildToAdd($child, $skip_sanitize);
|
||||
if ($prepend) array_unshift($this->children, $child);
|
||||
else $this->children[] = $child;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeChild(
|
||||
NodeInterface|Stringable|string $child
|
||||
): static {
|
||||
$this->children = array_filter(
|
||||
$this->children,
|
||||
function (NodeInterface $e) use ($child) {
|
||||
if (is_object($child)) $keep = $e !== $child;
|
||||
else $keep = $e != $child;
|
||||
if (!$keep) {
|
||||
$e->setParent(null);
|
||||
}
|
||||
return $keep;
|
||||
}
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addChildBefore(
|
||||
NodeInterface|Stringable|string $new_child,
|
||||
NodeInterface|Stringable|string $before_child,
|
||||
bool $skip_sanitize = false
|
||||
): static {
|
||||
$i = $this->indexOfChild($before_child);
|
||||
if ($i === null) {
|
||||
throw new Exception('Reference child not found in this container');
|
||||
}
|
||||
$new_child = $this->prepareChildToAdd($new_child, $skip_sanitize);
|
||||
array_splice($this->children, $i, 0, [$new_child]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addChildAfter(
|
||||
NodeInterface|Stringable|string $new_child,
|
||||
NodeInterface|Stringable|string $after_child,
|
||||
bool $skip_sanitize = false
|
||||
): static {
|
||||
$i = $this->indexOfChild($after_child);
|
||||
if ($i === null) {
|
||||
throw new Exception('Reference child not found in this container');
|
||||
}
|
||||
$new_child = $this->prepareChildToAdd($new_child, $skip_sanitize);
|
||||
array_splice($this->children, $i + 1, 0, [$new_child]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function prepareChildToAdd(NodeInterface|Stringable|string $child, bool $skip_sanitize): NodeInterface
|
||||
{
|
||||
// turn strings into nodes
|
||||
if (!($child instanceof NodeInterface)) {
|
||||
if ($skip_sanitize) $child = new UnsanitizedText($child);
|
||||
else $child = new Text($child);
|
||||
}
|
||||
// remove from parent, move it here, and return
|
||||
if ($parent = $child->parent()) {
|
||||
$parent->removeChild($child);
|
||||
}
|
||||
$child->setParent($this);
|
||||
return $child;
|
||||
}
|
||||
|
||||
protected function indexOfChild(NodeInterface|Stringable|string $child): null|int
|
||||
{
|
||||
if ($child instanceof NodeInterface) {
|
||||
foreach ($this->children() as $i => $v) {
|
||||
if ($v === $child) return $i;
|
||||
}
|
||||
} else {
|
||||
foreach ($this->children() as $i => $v) {
|
||||
if ($v == $child) return $i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,60 +3,47 @@
|
|||
namespace ByJoby\HTML\Traits;
|
||||
|
||||
use ByJoby\HTML\ContainerInterface;
|
||||
use ByJoby\HTML\ContainerMutableInterface;
|
||||
use ByJoby\HTML\Containers\DocumentInterface;
|
||||
use ByJoby\HTML\NodeInterface;
|
||||
use ByJoby\HTML\Tags\TagInterface;
|
||||
use DeepCopy\DeepCopy;
|
||||
use Exception;
|
||||
|
||||
trait NodeTrait
|
||||
{
|
||||
/** @var null|NodeInterface */
|
||||
/** @var null|ContainerInterface */
|
||||
protected $parent;
|
||||
/** @var null|DocumentInterface */
|
||||
protected $document;
|
||||
|
||||
abstract function __toString();
|
||||
|
||||
public function parent(): null|NodeInterface
|
||||
public function parent(): null|ContainerInterface
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function setParent(null|NodeInterface $parent): static
|
||||
public function setParent(null|ContainerInterface $parent): static
|
||||
{
|
||||
$this->parent = $parent;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function document(): null|DocumentInterface
|
||||
public function parentTag(): null|TagInterface
|
||||
{
|
||||
return $this->document;
|
||||
return $this->parentOfType(TagInterface::class); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
public function setDocument(null|DocumentInterface $document): static
|
||||
public function parentDocument(): null|DocumentInterface
|
||||
{
|
||||
$this->document = $document;
|
||||
if ($this instanceof ContainerInterface) {
|
||||
foreach ($this->children() as $child) {
|
||||
$child->setDocument($document);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
return $this->parentOfType(DocumentInterface::class); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
public function detach(): static
|
||||
public function parentOfType(string $class): mixed
|
||||
{
|
||||
$parent = $this->parent() ?? $this->document();
|
||||
if ($parent === null) {
|
||||
return $this;
|
||||
} elseif ($parent instanceof ContainerMutableInterface) {
|
||||
$parent->removeChild($this);
|
||||
$this->setParent(null);
|
||||
$this->setDocument(null);
|
||||
return $this;
|
||||
if ($this->parent() instanceof $class) {
|
||||
return $this->parent();
|
||||
} elseif ($this->parent() && $this->parent() instanceof NodeInterface) {
|
||||
return $this->parent()->parentOfType($class);
|
||||
} else {
|
||||
throw new Exception('Cannot detach() a Node from a parent that is not a ContainerMutableInterface, use detachCopy() instead');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +52,6 @@ trait NodeTrait
|
|||
static $copier;
|
||||
$copier = $copier ?? new DeepCopy();
|
||||
return ($copier->copy($this))
|
||||
->setParent(null)
|
||||
->setDocument(null);
|
||||
->setParent(null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ trait TagTrait
|
|||
|
||||
public function __toString(): string
|
||||
{
|
||||
return sprintf('<%s/>', implode(' ', $this->openingTagStrings()));
|
||||
return sprintf('<%s>', implode(' ', $this->openingTagStrings()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,8 +81,11 @@ trait TagTrait
|
|||
$strings[] = sprintf('style="%s"', $this->styles());
|
||||
}
|
||||
foreach ($this->attributes() as $name => $value) {
|
||||
if ($value === null) $strings[] = $name;
|
||||
else $strings[] = sprintf('%s="%s"', $name, static::sanitizeAttribute($value));
|
||||
if (is_string($value)) {
|
||||
$strings[] = sprintf('%s="%s"', $name, static::sanitizeAttribute($value));
|
||||
} elseif ($value) {
|
||||
$strings[] = $name;
|
||||
}
|
||||
}
|
||||
return $strings;
|
||||
}
|
||||
|
|
|
@ -12,11 +12,4 @@ class HeadTagTest extends TestCase
|
|||
$this->assertInstanceOf(TitleTagInterface::class, $head->title());
|
||||
$this->assertEquals($head, $head->title()->parent());
|
||||
}
|
||||
|
||||
public function testNoTitleDetach()
|
||||
{
|
||||
$head = new HeadTag;
|
||||
$this->expectExceptionMessage('Not allowed to detach TitleTag');
|
||||
$head->title()->detach();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,42 +30,17 @@ class FragmentTest extends TestCase
|
|||
$div2 = $this->tag('div');
|
||||
// adding div1 to fragment sets its fragment
|
||||
$fragment->addChild($div1);
|
||||
$this->assertEquals($fragment, $div1->document());
|
||||
$this->assertEquals($fragment, $div1->parentDocument());
|
||||
// adding div2 to div1 sets its document
|
||||
$div1->addChild($div2);
|
||||
$this->assertEquals($fragment, $div2->document());
|
||||
$this->assertEquals($fragment, $div2->parentDocument());
|
||||
// div2's parent tag should be div1
|
||||
$this->assertEquals($div1, $div2->parentTag());
|
||||
// div1 should not have a parent tag
|
||||
$this->assertNull($div1->parentTag());
|
||||
return $fragment;
|
||||
}
|
||||
|
||||
/** @depends clone testNestingDocument */
|
||||
public function testDetaching(Fragment $fragment): void
|
||||
{
|
||||
/** @var AbstractContainerTag */
|
||||
$div1 = $fragment->children()[0];
|
||||
/** @var AbstractContainerTag */
|
||||
$div2 = $div1->children()[0];
|
||||
// add a span and verify it has the right parent
|
||||
$span = $this->tag('span');
|
||||
$div2->addChild($span);
|
||||
$this->assertEquals($fragment, $span->document());
|
||||
// detach and check document/parent of all nodes
|
||||
$div1->detach();
|
||||
$this->assertNull($div1->document());
|
||||
$this->assertNull($div1->parent());
|
||||
$this->assertNull($div2->document());
|
||||
$this->assertEquals($div1, $div2->parent());
|
||||
$this->assertNull($span->document());
|
||||
$this->assertEquals($div2, $span->parent());
|
||||
// try detaching again, to verify detaching a detached node does nothing
|
||||
$div1->detach();
|
||||
$this->assertNull($div1->document());
|
||||
$this->assertNull($div1->parent());
|
||||
$this->assertNull($div2->document());
|
||||
$this->assertEquals($div1, $div2->parent());
|
||||
$this->assertNull($span->document());
|
||||
$this->assertEquals($div2, $span->parent());
|
||||
}
|
||||
|
||||
public function testMovingChild(): void
|
||||
{
|
||||
$fragment = new Fragment(['a', 'b']);
|
||||
|
@ -83,9 +58,9 @@ class FragmentTest extends TestCase
|
|||
$div2->addChild('a');
|
||||
// add child before a
|
||||
$div2->addChildBefore('b', 'a');
|
||||
$this->assertEquals($fragment, $div2->children()[0]->document());
|
||||
$this->assertEquals($fragment, $div2->children()[0]->parentDocument());
|
||||
// add child after a
|
||||
$div2->addChildAfter('c', 'a');
|
||||
$this->assertEquals($fragment, $div2->children()[2]->document());
|
||||
$this->assertEquals($fragment, $div2->children()[2]->parentDocument());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,10 @@ class GenericHtmlDocumentTest extends TestCase
|
|||
$this->assertTrue($document->body() === $document->html()->body());
|
||||
$this->assertTrue($document->head() === $document->html()->head());
|
||||
// everything has the correct document
|
||||
$this->assertEquals($document, $document->doctype()->document());
|
||||
$this->assertEquals($document, $document->html()->document());
|
||||
$this->assertEquals($document, $document->body()->document());
|
||||
$this->assertEquals($document, $document->head()->document());
|
||||
$this->assertEquals($document, $document->doctype()->parentDocument());
|
||||
$this->assertEquals($document, $document->html()->parentDocument());
|
||||
$this->assertEquals($document, $document->body()->parentDocument());
|
||||
$this->assertEquals($document, $document->head()->parentDocument());
|
||||
// children are doctype and html
|
||||
$this->assertEquals([$document->doctype(), $document->html()], $document->children());
|
||||
// string version of an empty document
|
||||
|
@ -45,18 +45,4 @@ class GenericHtmlDocumentTest extends TestCase
|
|||
$document->__toString()
|
||||
);
|
||||
}
|
||||
|
||||
public function testNoDoctypeDetach(): void
|
||||
{
|
||||
$document = new GenericHtmlDocument;
|
||||
$this->expectExceptionMessage('Cannot detach() a Node from a parent that is not a ContainerMutableInterface, use detachCopy() instead');
|
||||
$document->doctype()->detach();
|
||||
}
|
||||
|
||||
public function testNoHtmlDetach(): void
|
||||
{
|
||||
$document = new GenericHtmlDocument;
|
||||
$this->expectExceptionMessage('Cannot detach() a Node from a parent that is not a ContainerMutableInterface, use detachCopy() instead');
|
||||
$document->html()->detach();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,18 +10,18 @@ class AttributesTest extends TestCase
|
|||
{
|
||||
$attributes = new Attributes();
|
||||
$this->assertEquals([], $attributes->getArray());
|
||||
$attributes = new Attributes(['foo' => 'bar', 'baz' => null]);
|
||||
$this->assertEquals(['baz' => null, 'foo' => 'bar'], $attributes->getArray());
|
||||
$attributes = new Attributes(['foo' => 'bar', 'baz' => true]);
|
||||
$this->assertEquals(['baz' => true, 'foo' => 'bar'], $attributes->getArray());
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
public function testInvalidConstructionEmptyName(): Attributes
|
||||
public function testInvalidConstructionEmptyName(): void
|
||||
{
|
||||
$this->expectExceptionMessage('Attribute name must be specified when setting');
|
||||
$attributes = new Attributes(['' => 'foo']);
|
||||
}
|
||||
|
||||
public function testInvalidConstructionInvalidName(): Attributes
|
||||
public function testInvalidConstructionInvalidName(): void
|
||||
{
|
||||
$this->expectExceptionMessage('Invalid character in attribute name');
|
||||
$attributes = new Attributes(['a=b' => 'foo']);
|
||||
|
@ -34,7 +34,7 @@ class AttributesTest extends TestCase
|
|||
{
|
||||
$attributes['a'] = 'b';
|
||||
$this->assertEquals('b', $attributes['a']);
|
||||
$this->assertEquals(['a' => 'b', 'baz' => null, 'foo' => 'bar'], $attributes->getArray());
|
||||
$this->assertEquals(['a' => 'b', 'baz' => true, 'foo' => 'bar'], $attributes->getArray());
|
||||
unset($attributes['baz']);
|
||||
$this->assertEquals(['a' => 'b', 'foo' => 'bar'], $attributes->getArray());
|
||||
}
|
||||
|
@ -44,9 +44,16 @@ class AttributesTest extends TestCase
|
|||
*/
|
||||
public function testOffsetExists(Attributes $attributes): void
|
||||
{
|
||||
// test with a regular string
|
||||
$this->assertFalse(isset($attributes['a']));
|
||||
$attributes['a'] = 'b';
|
||||
$this->assertTrue(isset($attributes['a']));
|
||||
// test with an empty string
|
||||
$attributes['b'] = '';
|
||||
$this->assertTrue(isset($attributes['b']));
|
||||
// test with a null value
|
||||
$attributes['c'] = null;
|
||||
$this->assertFalse(isset($attributes['c']));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
20
tests/Html5/Tags/ScriptTagTest.php
Normal file
20
tests/Html5/Tags/ScriptTagTest.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace ByJoby\HTML\Html5\Tags;
|
||||
|
||||
class ScriptTagTest extends TagTestCase
|
||||
{
|
||||
public function testAttributeHelpers(): void
|
||||
{
|
||||
$this->assertAttributeHelperMethods('crossorigin', ScriptTag::class);
|
||||
$this->assertAttributeHelperMethods('integrity', ScriptTag::class);
|
||||
$this->assertAttributeHelperMethods('integrity', ScriptTag::class);
|
||||
$this->assertAttributeHelperMethods('nonce', ScriptTag::class);
|
||||
$this->assertAttributeHelperMethods('referrerpolicy', ScriptTag::class);
|
||||
$this->assertAttributeHelperMethods('src', ScriptTag::class);
|
||||
$this->assertAttributeHelperMethods('type', ScriptTag::class);
|
||||
$this->assertBooleanAttributeHelperMethods('async', ScriptTag::class);
|
||||
$this->assertBooleanAttributeHelperMethods('defer', ScriptTag::class);
|
||||
$this->assertBooleanAttributeHelperMethods('nomodule', ScriptTag::class);
|
||||
}
|
||||
}
|
|
@ -28,6 +28,23 @@ abstract class TagTestCase extends TestCase
|
|||
$this->assertNull(call_user_func([$tag, $getFn]));
|
||||
}
|
||||
|
||||
protected function assertBooleanAttributeHelperMethods(string $attribute, string $class): void
|
||||
{
|
||||
/** @var TagInterface */
|
||||
$tag = new $class;
|
||||
$words = explode('-', $attribute);
|
||||
$setFn = 'set' . implode('', array_map('ucfirst', $words));
|
||||
$getFn = array_shift($words) . implode('', array_map('ucfirst', $words));
|
||||
// test setting true
|
||||
call_user_func([$tag, $setFn], true);
|
||||
$this->assertTagRendersBooleanAttribute($tag, $attribute, true);
|
||||
$this->assertTrue(call_user_func([$tag, $getFn]));
|
||||
// test setting false
|
||||
call_user_func([$tag, $setFn], false);
|
||||
$this->assertTagRendersBooleanAttribute($tag, $attribute, false);
|
||||
$this->assertFalse(call_user_func([$tag, $getFn]));
|
||||
}
|
||||
|
||||
protected function assertTagRendersAttribute(TagInterface $tag, string $attribute, string $value)
|
||||
{
|
||||
if ($tag instanceof ContainerInterface || $tag instanceof ContentTagInterface) {
|
||||
|
@ -38,10 +55,43 @@ abstract class TagTestCase extends TestCase
|
|||
);
|
||||
} else {
|
||||
$this->assertEquals(
|
||||
sprintf('<%s %s="%s"/>', $tag->tag(), $attribute, $value),
|
||||
sprintf('<%s %s="%s">', $tag->tag(), $attribute, $value),
|
||||
$tag->__toString(),
|
||||
sprintf('Unexpected rendering of %s value %s is in %s tag', $attribute, $value, $tag->tag())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function assertTagRendersBooleanAttribute(TagInterface $tag, string $attribute, bool $value)
|
||||
{
|
||||
if ($tag instanceof ContainerInterface || $tag instanceof ContentTagInterface) {
|
||||
if ($value) {
|
||||
$this->assertEquals(
|
||||
sprintf('<%s %s></%s>', $tag->tag(), $attribute, $tag->tag()),
|
||||
$tag->__toString(),
|
||||
sprintf('Unexpected rendering of %s value %s is in %s tag', $attribute, $value ? 'true' : 'false', $tag->tag())
|
||||
);
|
||||
} else {
|
||||
$this->assertEquals(
|
||||
sprintf('<%s></%s>', $tag->tag(), $tag->tag()),
|
||||
$tag->__toString(),
|
||||
sprintf('Unexpected rendering of %s value %s is in %s tag', $attribute, $value ? 'true' : 'false', $tag->tag())
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if ($value) {
|
||||
$this->assertEquals(
|
||||
sprintf('<%s %s>', $tag->tag(), $attribute),
|
||||
$tag->__toString(),
|
||||
sprintf('Unexpected rendering of %s value %s is in %s tag', $attribute, $value ? 'true' : 'false', $tag->tag())
|
||||
);
|
||||
} else {
|
||||
$this->assertEquals(
|
||||
sprintf('<%s>', $tag->tag()),
|
||||
$tag->__toString(),
|
||||
sprintf('Unexpected rendering of %s value %s is in %s tag', $attribute, $value ? 'true' : 'false', $tag->tag())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,4 +13,12 @@ class CommentTest extends TestCase
|
|||
$this->assertEquals('<!-- foo-bar -->', new Comment('foo-bar'));
|
||||
$this->assertNotEquals('<!-- foo--bar -->', new Comment('foo--bar'));
|
||||
}
|
||||
|
||||
public function testModification(): void
|
||||
{
|
||||
$comment = new Comment('foo');
|
||||
$this->assertEquals('foo', $comment->value());
|
||||
$comment->setValue('bar');
|
||||
$this->assertEquals('bar', $comment->value());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,4 +12,12 @@ class TextTest extends TestCase
|
|||
$this->assertEquals('foo', new Text('foo'));
|
||||
$this->assertEquals('foo', new Text('<strong>foo</strong>'));
|
||||
}
|
||||
|
||||
public function testModification(): void
|
||||
{
|
||||
$text = new Text('foo');
|
||||
$this->assertEquals('foo', $text->value());
|
||||
$text->setValue('bar');
|
||||
$this->assertEquals('bar', $text->value());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,4 +12,12 @@ class UnsanitizedTextTest extends TestCase
|
|||
$this->assertEquals('foo', new UnsanitizedText('foo'));
|
||||
$this->assertEquals('<strong>foo</strong>', new UnsanitizedText('<strong>foo</strong>'));
|
||||
}
|
||||
|
||||
public function testModification(): void
|
||||
{
|
||||
$text = new UnsanitizedText('foo');
|
||||
$this->assertEquals('foo', $text->value());
|
||||
$text->setValue('bar');
|
||||
$this->assertEquals('bar', $text->value());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,15 @@ class AbstractContainerTagTest extends TestCase
|
|||
return $div;
|
||||
}
|
||||
|
||||
public function testBooleanAttributes(): void
|
||||
{
|
||||
$tag = $this->tag('div');
|
||||
$tag->attributes()['a'] = true;
|
||||
$tag->attributes()['b'] = false;
|
||||
$tag->attributes()['c'] = null;
|
||||
$this->assertEquals('<div a></div>', $tag->__toString());
|
||||
}
|
||||
|
||||
/** @depends clone testDIV */
|
||||
public function testMoreNesting(AbstractContainerTag $div): AbstractContainerTag
|
||||
{
|
||||
|
@ -79,16 +88,6 @@ class AbstractContainerTagTest extends TestCase
|
|||
$this->assertInstanceOf(UnsanitizedText::class, $div->children()[2]);
|
||||
}
|
||||
|
||||
/** @depends clone testMoreNesting */
|
||||
public function testDetach(AbstractContainerTag $div): void
|
||||
{
|
||||
/** @var AbstractContainerTag */
|
||||
$span = $div->children()[0];
|
||||
$span->detach();
|
||||
$this->assertEquals('<div a="b"></div>', $div->__toString());
|
||||
$this->assertNull($span->parent());
|
||||
}
|
||||
|
||||
/** @depends clone testMoreNesting */
|
||||
public function testDetachCopy(AbstractContainerTag $div): void
|
||||
{
|
||||
|
|
|
@ -19,7 +19,7 @@ class AbstractTagTest extends TestCase
|
|||
public function testBR(): AbstractTag
|
||||
{
|
||||
$br = $this->tag('br');
|
||||
$this->assertEquals('<br/>', $br->__toString());
|
||||
$this->assertEquals('<br>', $br->__toString());
|
||||
$this->assertInstanceOf(Classes::class, $br->classes());
|
||||
$this->assertInstanceOf(Attributes::class, $br->attributes());
|
||||
return $br;
|
||||
|
@ -33,10 +33,10 @@ class AbstractTagTest extends TestCase
|
|||
$this->assertNull($tag->id());
|
||||
$tag->setID('foo');
|
||||
$this->assertEquals('foo', $tag->id());
|
||||
$this->assertEquals('<br id="foo"/>', $tag->__toString());
|
||||
$this->assertEquals('<br id="foo">', $tag->__toString());
|
||||
$tag->setID(null);
|
||||
$this->assertNull($tag->id());
|
||||
$this->assertEquals('<br/>', $tag->__toString());
|
||||
$this->assertEquals('<br>', $tag->__toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,12 +46,21 @@ class AbstractTagTest extends TestCase
|
|||
{
|
||||
$tag->attributes()['b'] = 'c';
|
||||
$tag->attributes()['a'] = 'b';
|
||||
$this->assertEquals('<br a="b" b="c"/>', $tag->__toString());
|
||||
$this->assertEquals('<br a="b" b="c">', $tag->__toString());
|
||||
unset($tag->attributes()['a']);
|
||||
$this->assertEquals('<br b="c"/>', $tag->__toString());
|
||||
$this->assertEquals('<br b="c">', $tag->__toString());
|
||||
$tag->classes()->add('some-class');
|
||||
$tag->styles()['style'] = 'value';
|
||||
$this->assertEquals('<br class="some-class" style="style:value" b="c"/>', $tag->__toString());
|
||||
$this->assertEquals('<br class="some-class" style="style:value" b="c">', $tag->__toString());
|
||||
}
|
||||
|
||||
public function testBooleanAttributes(): void
|
||||
{
|
||||
$tag = $this->tag('br');
|
||||
$tag->attributes()['a'] = true;
|
||||
$tag->attributes()['b'] = false;
|
||||
$tag->attributes()['c'] = null;
|
||||
$this->assertEquals('<br a>', $tag->__toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue