Skip to content

Commit

Permalink
Add view with html5 and page title
Browse files Browse the repository at this point in the history
  • Loading branch information
danon committed Mar 26, 2024
1 parent de3af14 commit 142d7b4
Show file tree
Hide file tree
Showing 9 changed files with 304 additions and 2 deletions.
9 changes: 7 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"psr-4": {
"Boduch\\Grid\\": "grid/",
"Coyote\\": "app/",
"Neon\\": "neon/src/",
"Database\\Seeders\\": "database/seeders/"
},
"files": [
Expand All @@ -83,8 +84,12 @@
"Tests\\Browser\\": "tests/Browser/",
"Tests\\Grid\\": "tests/Grid/",
"Tests\\Legacy\\": "tests/Legacy/",
"Tests\\Unit\\": "tests/Unit/"
}
"Tests\\Unit\\": "tests/Unit/",
"Neon\\Test\\": "neon/test/"
},
"files": [
"neon/test/BaseFixture/Caught/caught.php"
]
},
"scripts": {
"post-install-cmd": [
Expand Down
17 changes: 17 additions & 0 deletions neon/src/View.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
namespace Neon;

class View
{
public function __construct(readonly private string $title)
{
}

public function html(): string
{
return '<!DOCTYPE html>' .
'<html>' .
'<title>' . $this->title . '</title>' .
'</html>';
}
}
32 changes: 32 additions & 0 deletions neon/test/BaseFixture/Caught/CaughtTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
namespace Neon\Test\BaseFixture\Caught;

use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\TestCase;

class CaughtTest extends TestCase
{
/**
* @test
*/
public function caught(): void
{
$exception = caught(function () {
throw new \Exception('foo');
});
$this->assertSame('foo', $exception->getMessage());
}

/**
* @test
*/
public function noException(): void
{
try {
caught(function () {
});
} catch (AssertionFailedError $exception) {
$this->assertSame('Failed to assert that exception is thrown.', $exception->getMessage());
}
}
}
14 changes: 14 additions & 0 deletions neon/test/BaseFixture/Caught/caught.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
namespace Neon\Test\BaseFixture\Caught;

use PHPUnit\Framework\AssertionFailedError;

function caught(callable $block): \Throwable
{
try {
$block();
} catch (\Throwable $throwable) {
return $throwable;
}
throw new AssertionFailedError('Failed to assert that exception is thrown.');
}
47 changes: 47 additions & 0 deletions neon/test/BaseFixture/View/ViewDom.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
namespace Neon\Test\BaseFixture\View;

readonly class ViewDom
{
private \DOMDocument $document;
private \DOMXPath $xPath;

public function __construct(private string $html)
{
$this->document = new \DOMDocument();
\libxml_use_internal_errors(true);
$this->document->loadHTML($this->html);
\libxml_clear_errors();
$this->xPath = new \DOMXPath($this->document);
}

public function find(string $xPath): string
{
$first = $this->first($this->xPath->query($xPath), $xPath);
return $first->textContent;
}

private function first(\DOMNodeList $nodes, string $xPath): \DOMElement|\DOMText
{
$count = $nodes->count();
if ($count === 0) {
throw $this->exception('Failed to find element', $xPath);
}
if ($count === 1) {
return $nodes[0];
}
throw $this->exception("Failed to find unique element (found $count)", $xPath);
}

private function exception(string $summary, string $xPath): \Exception
{
$structure = new ViewDomStructure($this->document);
return new \Exception("$summary: $xPath\n\n" . $structure->structure());
}

public function docType(): string
{
$node = $this->document->getRootNode();
return \strStr($this->document->saveHTML($node), "\n", true);
}
}
82 changes: 82 additions & 0 deletions neon/test/BaseFixture/View/ViewDomFindTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php
namespace Neon\Test\BaseFixture\View;

use PHPUnit\Framework\TestCase;
use function Neon\Test\BaseFixture\Caught\caught;

class ViewDomFindTest extends TestCase
{
/**
* @test
*/
public function textContent(): void
{
$dom = new ViewDom('<ul><li>Winter is coming</li><ul>');
$this->assertSame('Winter is coming', $dom->find('/html/body/ul/li'));
}

/**
* @test
*/
public function notFound(): void
{
$dom = new ViewDom('<p>Missing</p>');
$throwable = caught(fn() => $dom->find('/html/body/ul/li'));
$this->assertStringStartsWith('Failed to find element: /html/body/ul/li', $throwable->getMessage());
}

/**
* @test
*/
public function many(): void
{
$dom = new ViewDom('<p>One</p><p>Two</p>');
$throwable = caught(fn() => $dom->find('//p'));
$this->assertStringStartsWith('Failed to find unique element (found 2): //p', $throwable->getMessage());
}

/**
* @test
*/
public function textNode(): void
{
$dom = new ViewDom('<p>We do not sow</p>');
$this->assertSame('We do not sow', $dom->find('//p/text()'));
}

/**
* @test
*/
public function htmlEntity(): void
{
$dom = new ViewDom('<p>&gt;</p>');
$this->assertSame('>', $dom->find('//p/text()'));
}

/**
* @test
*/
public function structureNotFound(): void
{
$throwable = $this->listItemException('<title>Foo</title> <p>Bar</p>');
$this->assertStringEndsWith(
'html(head(title),body(p))',
$throwable->getMessage());
}

/**
* @test
*/
public function structureMany(): void
{
$throwable = $this->listItemException('<ul><li>One</li><li>Two</li></ul>');
$this->assertStringEndsWith(
'html(body(ul(li,li)))',
$throwable->getMessage());
}

private function listItemException(string $html): \Throwable
{
return caught(fn() => (new ViewDom($html))->find('/html/body/ul/li'));
}
}
27 changes: 27 additions & 0 deletions neon/test/BaseFixture/View/ViewDomRootTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
namespace Neon\Test\BaseFixture\View;

use PHPUnit\Framework\TestCase;

class ViewDomRootTest extends TestCase
{
/**
* @test
*/
public function html5(): void
{
$dom = new ViewDom('<!DOCTYPE html><html></html>');
$this->assertSame('<!DOCTYPE html>', $dom->docType());
}

/**
* @test
*/
public function traditionalHtml(): void
{
$dom = new ViewDom('<p>Foo</p>');
$this->assertSame(
'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">',
$dom->docType());
}
}
44 changes: 44 additions & 0 deletions neon/test/BaseFixture/View/ViewDomStructure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
namespace Neon\Test\BaseFixture\View;

readonly class ViewDomStructure
{
public function __construct(private \DOMDocument $document)
{
}

public function structure(): string
{
return $this->elementAsString($this->htmlNode());
}

private function htmlNode(): \DOMElement
{
return $this->document->getElementsByTagName('html')[0];
}

private function elementAsString(\DOMElement $element): string
{
$childNames = $this->childrenAsStrings($element);
if (empty($childNames)) {
return $element->nodeName;
}
$children = \implode(',', $childNames);
return "{$element->nodeName}($children)";
}

/**
* @return string[]
*/
private function childrenAsStrings(\DOMElement $element): array
{
$childNames = [];
foreach ($element->childNodes as $child) {
/** @var \DOMNode $child */
if ($child->nodeType === \XML_ELEMENT_NODE) {
$childNames[] = $this->elementAsString($child);
}
}
return $childNames;
}
}
34 changes: 34 additions & 0 deletions neon/test/Unit/View/Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
namespace Neon\Test\Unit\View;

use Neon\Test\BaseFixture\View\ViewDom;
use Neon\View;
use PHPUnit\Framework\TestCase;

class Test extends TestCase
{
/**
* @test
*/
public function docType(): void
{
$dom = new ViewDom($this->view(''));
$this->assertSame('<!DOCTYPE html>', $dom->docType());
}

/**
* @test
*/
public function title(): void
{
$dom = new ViewDom($this->view('Winter is coming'));
$this->assertSame(
'Winter is coming',
$dom->find('/html/head/title'));
}

private function view(string $title): string
{
return (new View($title))->html();
}
}

0 comments on commit 142d7b4

Please sign in to comment.