started stubbing out HTML5 tags, added base test class for tag-specific tests

This commit is contained in:
Joby 2022-11-30 22:35:29 -07:00
parent 56658e5a47
commit 7f42a0cfca
18 changed files with 209 additions and 69 deletions

View file

@ -1,9 +0,0 @@
language: php
php:
- 7.1
- 7.2
- 7.3
- 7.4
install:
- composer install
script: composer test

View file

@ -6,8 +6,5 @@ use ByJoby\HTML\Tags\AbstractContainerTag;
class BodyTag extends AbstractContainerTag implements BodyTagInterface
{
public function tag(): string
{
return 'body';
}
const TAG = 'body';
}

View file

@ -6,6 +6,8 @@ use ByJoby\HTML\Tags\AbstractContainerTag;
class HeadTag extends AbstractContainerTag implements HeadTagInterface
{
const TAG = 'head';
/** @var TitleTagInterface */
protected $title;
@ -20,9 +22,4 @@ class HeadTag extends AbstractContainerTag implements HeadTagInterface
{
return $this->title;
}
public function tag(): string
{
return 'head';
}
}

View file

@ -6,6 +6,8 @@ use ByJoby\HTML\Tags\AbstractContainerTag;
class HtmlTag extends AbstractContainerTag implements HtmlTagInterface
{
const TAG = 'html';
/** @var HeadTagInterface */
protected $head;
/** @var BodyTagInterface */
@ -26,11 +28,6 @@ class HtmlTag extends AbstractContainerTag implements HtmlTagInterface
];
}
public function tag(): string
{
return 'html';
}
public function head(): HeadTagInterface
{
return $this->head;

View file

@ -2,12 +2,13 @@
namespace ByJoby\HTML\Containers\DocumentTags;
use ByJoby\HTML\Tags\AbstractContainerTag;
use ByJoby\HTML\Traits\NodeTrait;
use Exception;
class TitleTag implements TitleTagInterface
{
const TAG = 'title';
use NodeTrait;
/** @var string */

View file

@ -47,7 +47,7 @@ class Attributes implements IteratorAggregate, ArrayAccess
function offsetGet(mixed $offset): mixed
{
$offset = static::sanitizeOffset($offset);
return $this->array[$offset];
return @$this->array[$offset];
}
function offsetSet(mixed $offset, mixed $value): void

View file

@ -0,0 +1,9 @@
<?php
namespace ByJoby\HTML\Html5;
use ByJoby\HTML\Containers\GenericHtmlDocument;
class Html5Document extends GenericHtmlDocument
{
}

View file

@ -0,0 +1,44 @@
<?php
namespace ByJoby\HTML\Html5\Tags;
use ByJoby\HTML\Tags\AbstractTag;
class BaseTag extends AbstractTag
{
const TAG = 'base';
public function href(): null|string
{
return $this->attributes()['href'];
}
public function setHref(null|string $href): static
{
$this->attributes()['href'] = $href;
return $this;
}
public function unsetHref(): static
{
unset($this->attributes()['href']);
return $this;
}
public function target(): null|string
{
return $this->attributes()['target'];
}
public function setTarget(null|string $target): static
{
$this->attributes()['target'] = $target;
return $this;
}
public function unsetTarget(): static
{
unset($this->attributes()['target']);
return $this;
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace ByJoby\HTML\Html5\Tags;
use ByJoby\HTML\Tags\AbstractTag;
class LinkTag extends AbstractTag
{
const TAG = 'link';
}

View file

@ -0,0 +1,10 @@
<?php
namespace ByJoby\HTML\Html5\Tags;
use ByJoby\HTML\Tags\AbstractTag;
class MetaTag extends AbstractTag
{
const TAG = 'meta';
}

View file

@ -0,0 +1,11 @@
<?php
namespace ByJoby\HTML\Html5\Tags;
use ByJoby\HTML\Tags\AbstractTag;
// TODO: implement some sort of AbstractContentTag to hold non-HTML content like styles (maybe also make the TitleTag use this)
class StyleTag extends AbstractTag
{
const TAG = 'style';
}

View file

@ -2,8 +2,6 @@
namespace ByJoby\HTML\Tags;
use ByJoby\HTML\Helpers\Attributes;
use ByJoby\HTML\Helpers\Classes;
use ByJoby\HTML\Traits\ContainerMutableTrait;
use ByJoby\HTML\Traits\ContainerTagTrait;
use ByJoby\HTML\Traits\ContainerTrait;
@ -17,4 +15,9 @@ abstract class AbstractContainerTag implements ContainerTagInterface
use ContainerTagTrait {
ContainerTagTrait::__toString insteadof TagTrait;
}
public function tag(): string
{
return static::TAG; //@phpstan-ignore-line
}
}

View file

@ -2,12 +2,15 @@
namespace ByJoby\HTML\Tags;
use ByJoby\HTML\Helpers\Attributes;
use ByJoby\HTML\Helpers\Classes;
use ByJoby\HTML\Traits\TagTrait;
use ByJoby\HTML\Traits\NodeTrait;
abstract class AbstractTag implements TagInterface
{
use NodeTrait, TagTrait;
public function tag(): string
{
return static::TAG; //@phpstan-ignore-line
}
}

View file

@ -8,6 +8,13 @@ use PHPUnit\Framework\TestCase;
class FragmentTest extends TestCase
{
public function tag(string $name): AbstractContainerTag
{
$tag = $this->getMockForAbstractClass(AbstractContainerTag::class, [], '', true, true, true, ['tag']);
$tag->method('tag')->willReturn($name);
return $tag;
}
public function testConstruction()
{
$empty = new Fragment();
@ -19,10 +26,8 @@ class FragmentTest extends TestCase
public function testNestingDocument(): Fragment
{
$fragment = new Fragment();
$div1 = $this->getMockForAbstractClass(AbstractContainerTag::class);
$div1->method('tag')->will($this->returnValue('div'));
$div2 = $this->getMockForAbstractClass(AbstractContainerTag::class);
$div2->method('tag')->will($this->returnValue('div'));
$div1 = $this->tag('div');
$div2 = $this->tag('div');
// adding div1 to fragment sets its fragment
$fragment->addChild($div1);
$this->assertEquals($fragment, $div1->document());
@ -40,8 +45,7 @@ class FragmentTest extends TestCase
/** @var AbstractContainerTag */
$div2 = $div1->children()[0];
// add a span and verify it has the right parent
$span = $this->getMockForAbstractClass(AbstractTag::class);
$span->method('tag')->will($this->returnValue('span'));
$span = $this->tag('span');
$div2->addChild($span);
$this->assertEquals($fragment, $span->document());
// detach and check document/parent of all nodes

View file

@ -0,0 +1,12 @@
<?php
namespace ByJoby\HTML\Html5\Tags;
class BaseTagTest extends TagTestCase
{
public function testAttributeHelpers(): void
{
$this->assertAttributeHelperMethods('href', BaseTag::class);
$this->assertAttributeHelperMethods('target', BaseTag::class);
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace ByJoby\HTML\Html5\Tags;
use ByJoby\HTML\ContainerInterface;
use ByJoby\HTML\Tags\TagInterface;
use PHPUnit\Framework\TestCase;
abstract class TagTestCase extends TestCase
{
protected function assertAttributeHelperMethods(string $attribute, string $class, string $test_value = 'some-value', string $render_value = 'some-value'): void
{
/** @var TagInterface */
$tag = new $class;
$words = explode('-', $attribute);
$setFn = 'set' . implode('', array_map('ucfirst', $words));
$unsetFn = 'unset' . implode('', array_map('ucfirst', $words));
$getFn = array_shift($words) . implode('', array_map('ucfirst', $words));
// test setter and expected rendering
call_user_func([$tag, $setFn], $test_value);
$this->assertTagRendersAttribute($tag, $attribute, $render_value);
// test getter
$this->assertEquals($render_value, call_user_func([$tag, $getFn]));
$this->assertEquals($render_value, $tag->attributes()[$attribute]);
// test unsetting via unset
call_user_func([$tag,$unsetFn]);
$this->assertNull(call_user_func([$tag,$getFn]));
}
protected function assertTagRendersAttribute(TagInterface $tag, string $attribute, string $value)
{
if ($tag instanceof ContainerInterface) {
$this->assertEquals(
sprintf('<%s %s="%s"></%s>', $tag->tag(), $attribute, $value, $tag->tag()),
$tag->__toString(),
sprintf('Unexpected rendering of %s value %s is in %s tag', $attribute, $value, $tag->tag())
);
} else {
$this->assertEquals(
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())
);
}
}
}

View file

@ -2,21 +2,24 @@
namespace ByJoby\HTML\Tags;
use ByJoby\HTML\Helpers\Attributes;
use ByJoby\HTML\Helpers\Classes;
use ByJoby\HTML\Nodes\Text;
use ByJoby\HTML\Nodes\UnsanitizedText;
use PHPUnit\Framework\TestCase;
class AbstractContainerTagTest extends TestCase
{
public function tag(string $name): AbstractContainerTag
{
$tag = $this->getMockForAbstractClass(AbstractContainerTag::class, [], '', true, true, true, ['tag']);
$tag->method('tag')->willReturn($name);
return $tag;
}
public function testDIV(): AbstractContainerTag
{
$div = $this->getMockForAbstractClass(AbstractContainerTag::class);
$div->method('tag')->will($this->returnValue('div'));
$div = $this->tag('div');
$this->assertEquals('<div></div>', $div->__toString());
$span = $this->getMockForAbstractClass(AbstractContainerTag::class);
$span->method('tag')->will($this->returnValue('span'));
$span = $this->tag('span');
$div->addChild($span);
$div->attributes()['a'] = 'b';
$this->assertEquals(
@ -34,9 +37,9 @@ class AbstractContainerTagTest extends TestCase
/** @depends clone testDIV */
public function testMoreNesting(AbstractContainerTag $div): AbstractContainerTag
{
/** @var AbstractContainerTag */
$span1 = $div->children()[0];
$span2 = $this->getMockForAbstractClass(AbstractContainerTag::class);
$span2->method('tag')->will($this->returnValue('span'));
$span2 = $this->tag('span');
$span1->addChild($span2);
$this->assertEquals(
implode(PHP_EOL, [
@ -54,8 +57,7 @@ class AbstractContainerTagTest extends TestCase
/** @depends clone testDIV */
public function testRemoveChild(AbstractContainerTag $div): void
{
$span2 = $this->getMockForAbstractClass(AbstractContainerTag::class);
$span2->method('tag')->will($this->returnValue('span'));
$span2 = $this->tag('span');
// add a second span and remove it using its object
$div->addChild($span2);
$this->assertCount(2, $div->children());
@ -80,38 +82,36 @@ class AbstractContainerTagTest extends TestCase
/** @depends clone testMoreNesting */
public function testDetach(AbstractContainerTag $div): void
{
$span1 = $div->children()[0];
$span2 = $span1->children()[0];
$span1->detach();
/** @var AbstractContainerTag */
$span = $div->children()[0];
$span->detach();
$this->assertEquals('<div a="b"></div>', $div->__toString());
$this->assertNull($span1->parent());
$this->assertNull($span->parent());
}
/** @depends clone testMoreNesting */
public function testDetachCopy(AbstractContainerTag $div): void
{
$span1 = $div->children()[0];
$span2 = $span1->children()[0];
$copy = $span1->detachCopy();
/** @var AbstractContainerTag */
$span = $div->children()[0];
$copy = $span->detachCopy();
$this->assertNull($copy->parent());
$this->assertEquals($div, $span1->parent());
$this->assertEquals($div, $span->parent());
}
public function testAddChildBefore(): void
{
$div = $this->getMockForAbstractClass(AbstractContainerTag::class);
$div->method('tag')->will($this->returnValue('div'));
$div = $this->tag('div');
// add a string child
$div->addChild('a');
$div->addChildBefore('b', 'a');
$this->assertEquals('b', $div->children()[0]->__toString());
// add an actual node object
$span1 = $this->getMockForAbstractClass(AbstractContainerTag::class);
$span1->method('tag')->will($this->returnValue('span'));
$div->addChildBefore($span1, 'a');
$this->assertEquals($span1, $div->children()[1]->__toString());
$span = $this->tag('span');
$div->addChildBefore($span, 'a');
$this->assertEquals($span, $div->children()[1]->__toString());
// add another object referencing the node object
$div->addChildBefore('c', $span1);
$div->addChildBefore('c', $span);
$this->assertEquals('c', $div->children()[1]->__toString());
// should throw an exception if reference child is not found
$this->expectExceptionMessage('Reference child not found in this container');
@ -120,19 +120,17 @@ class AbstractContainerTagTest extends TestCase
public function testAddChildAfter(): void
{
$div = $this->getMockForAbstractClass(AbstractContainerTag::class);
$div->method('tag')->will($this->returnValue('div'));
$div = $this->tag('div');
// add a string child
$div->addChild('a');
$div->addChildAfter('b', 'a');
$this->assertEquals('b', $div->children()[1]->__toString());
// add an actual node object
$span1 = $this->getMockForAbstractClass(AbstractContainerTag::class);
$span1->method('tag')->will($this->returnValue('span'));
$div->addChildAfter($span1, 'a');
$this->assertEquals($span1, $div->children()[1]->__toString());
$span = $this->tag('span');
$div->addChildAfter($span, 'a');
$this->assertEquals($span, $div->children()[1]->__toString());
// add another object referencing the node object
$div->addChildAfter('c', $span1);
$div->addChildAfter('c', $span);
$this->assertEquals('c', $div->children()[2]->__toString());
// should throw an exception if reference child is not found
$this->expectExceptionMessage('Reference child not found in this container');

View file

@ -8,10 +8,17 @@ use PHPUnit\Framework\TestCase;
class AbstractTagTest extends TestCase
{
public function tag(string $name): AbstractTag
{
$tag = $this->getMockForAbstractClass(AbstractTag::class, [], '', true, true, true, ['tag']);
$tag->method('tag')->willReturn($name);
return $tag;
}
public function testBR(): AbstractTag
{
$br = $this->getMockForAbstractClass(AbstractTag::class);
$br->method('tag')->will($this->returnValue('br'));
$br = $this->tag('br');
$this->assertEquals('<br/>', $br->__toString());
$this->assertInstanceOf(Classes::class, $br->classes());
$this->assertInstanceOf(Attributes::class, $br->attributes());