diff --git a/tests/Unit/HtmlTestingSupportMetaTest.php b/tests/Unit/HtmlTestingSupportMetaTest.php
new file mode 100644
index 00000000..4d7a92a5
--- /dev/null
+++ b/tests/Unit/HtmlTestingSupportMetaTest.php
@@ -0,0 +1,453 @@
+html ??= file_get_contents(Hyde::vendorPath('resources/views/homepages/welcome.blade.php'));
+ }
+
+ public function testHtmlHelper()
+ {
+ $this->assertInstanceOf(TestableHtmlDocument::class, $this->html($this->html));
+ }
+
+ public function testAssertSee()
+ {
+ $this->html($this->html)
+ ->assertSee('
Welcome to HydePHP!')
+ ->assertDontSee('Unwelcome to HydePHP!');
+ }
+
+ public function testAssertSeeEscaped()
+ {
+ $this->html(e('Foo
').'Bar
')
+ ->assertSeeEscaped('Foo
')
+ ->assertDontSeeEscaped('Bar
')
+ ->assertDontSee('Foo
')
+ ->assertSee('Bar
');
+ }
+
+ public function testTapElement()
+ {
+ $this->assertInstanceOf(TestableHtmlDocument::class,
+ $this->html($this->html)->tapElement('head > title', fn (TestableHtmlElement $element) => $element->assertSee('Welcome to HydePHP!'))
+ );
+ }
+
+ public function testTapElementUsingId()
+ {
+ $this->assertInstanceOf(TestableHtmlDocument::class,
+ $this->html('Foo
')->tapElement('#foo', fn (TestableHtmlElement $element) => $element->assertSee('Foo'))
+ );
+ }
+
+ public function testGetElementUsingQuery()
+ {
+ $this->assertInstanceOf(TestableHtmlElement::class,
+ $this->html($this->html)->getElementUsingQuery('head > title')->assertSee('Welcome to HydePHP!')
+ );
+ }
+
+ public function testGetRootElement()
+ {
+ $element = $this->html('Foo
')->getRootElement();
+
+ $this->assertInstanceOf(TestableHtmlElement::class, $element);
+ $this->assertSame('Foo
', $element->html);
+ }
+
+ public function testGetElementById()
+ {
+ $this->assertInstanceOf(TestableHtmlElement::class,
+ $this->html('Foo
')->getElementById('foo')->assertSee('Foo')
+ );
+
+ $this->assertNull($this->html('Foo
')->getElementById('bar'));
+ }
+
+ public function testElementUsingId()
+ {
+ $this->assertInstanceOf(TestableHtmlElement::class,
+ $this->html('')->element('#foo')->assertSee('Baz')
+ );
+
+ $this->assertNull($this->html('')->element('#bar'));
+ }
+
+ public function testElementUsingSelector()
+ {
+ $this->assertInstanceOf(TestableHtmlElement::class,
+ $this->html('Baz
')->element('foo > bar')->assertSee('Baz')
+ );
+
+ $this->assertNull($this->html('Baz
')->element('foo > baz'));
+ }
+
+ public function testElementUsingUnknownSyntax()
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage("The selector syntax 'foo' is not supported.");
+
+ $this->html('Baz')->element('foo');
+ }
+
+ public function testGetElementsByClass()
+ {
+ $this->assertCount(1, $this->html('Foo
')->getElementsByClass('foo'));
+ $this->assertCount(0, $this->html('Foo
')->getElementsByClass('bar'));
+ }
+
+ public function testGetElementsByClassTypes()
+ {
+ $document = $this->html('Foo
Bar
');
+
+ $collection = $document->getElementsByClass('foo');
+
+ $this->assertInstanceOf(Collection::class, $collection);
+ $this->assertContainsOnlyInstancesOf(TestableHtmlElement::class, $collection);
+ $this->assertSame(['Foo', 'Bar'], $collection->map->text->all());
+ }
+
+ public function testGetElementsByClassWithChildNodes()
+ {
+ $html = <<<'HTML'
+
+ HTML;
+
+ $collection = $this->html($html)->getElementsByClass('foo');
+
+ $this->assertCount(5, $collection);
+ $this->assertContainsOnlyInstancesOf(TestableHtmlElement::class, $collection);
+ $this->assertSame(['Foo', 'Bar', 'Baz'], $collection->map->text->filter()->values()->all());
+ }
+
+ public function testFluentClassAssertions()
+ {
+ $html = <<<'HTML'
+
+ HTML;
+
+ $collection = $this->html($html)->getElementsByClass('foo');
+
+ $this->assertSame(
+ $collection->each(fn (TestableHtmlElement $element) => $element->assertSee('Foo')),
+ $collection->each->assertSee('Foo')
+ );
+ }
+
+ public function testQuery()
+ {
+ $this->assertInstanceOf(TestableHtmlElement::class,
+ $this->html($this->html)->query('head > title')->assertSee('Welcome to HydePHP!')
+ );
+
+ $this->assertNull($this->html($this->html)->query('head > title > h1'));
+ }
+
+ public function testQueryWithEdgeCases()
+ {
+ $this->assertSame('foo', $this->html('')->query('')->tag);
+ $this->assertSame('bar', $this->html('')->query('bar')->tag);
+ $this->assertSame('bar', $this->html('')->query('bar')->tag);
+ $this->assertSame('bar', $this->html('Baz')->query('bar')->tag);
+
+ $this->assertSame('foo', $this->html('
')->query('foo')->tag);
+ $this->assertSame('bar', $this->html('
')->query('foo > bar')->tag);
+ $this->assertSame('bar', $this->html('
')->query('foo > bar')->tag);
+ $this->assertSame('bar', $this->html('Baz
')->query('foo > bar')->tag);
+ }
+
+ public function testDumpHelper()
+ {
+ $dump = $this->html($this->html)->dump(false);
+
+ $this->assertStringContainsString('Document Dump', $dump);
+ $this->assertStringContainsString('Document Preview', $dump);
+ $this->assertStringContainsString('Raw HTML', $dump);
+ $this->assertStringContainsString('Nodes', $dump);
+
+ $this->assertStringContainsString(e('Welcome to HydePHP!'), $dump);
+ }
+
+ public function testGetStructure()
+ {
+ $html = <<<'HTML'
+
+
+
+ HTML;
+
+ $expected = <<<'TXT'
+ main
+ div
+ h1
+ p
+ small
+ TXT;
+
+ $this->assertSame($expected, $this->html($html)->getStructure());
+ }
+
+ public function testGetTextRepresentation()
+ {
+ $html = <<<'HTML'
+
+
+
+ HTML;
+
+ $expected = <<<'TXT'
+ Foo
+ Bar Baz
+ TXT;
+
+ $this->assertSame($expected, $this->html($html)->getTextRepresentation());
+ }
+
+ public function testGetTextRepresentationWithMultipleLines()
+ {
+ $html = <<<'HTML'
+
+
+ Line 2
+
+
+
+ HTML;
+
+ $expected = <<<'TXT'
+ Foo
+ Bar Baz
+ Line 2
+ Line 3
+ Line 3
+ TXT;
+
+ $this->assertSame($expected, $this->html($html)->getTextRepresentation());
+ }
+
+ public function testComplexTextRepresentationParsing()
+ {
+ $expected = <<<'HTML'
+Welcome to HydePHP!
+You're running on HydePHP
+Leap into the future of static HTML blogs and documentation with the tools you already know and love.
+Made with Tailwind, Laravel, and Coffee.
+This is the default homepage stored as index.blade.php, however you can publish any of the built-in views using the following command:
+php hyde php hyde php hyde publish:homepage
+Resources for getting started
+Documentation
+Getting Started
+GitHub Source Code
+HTML;
+
+ $this->assertSame($expected, $this->html($this->html)->getTextRepresentation());
+ }
+
+ public function testAssertStructureLooksLike()
+ {
+ $html = <<<'HTML'
+
+
+
+ HTML;
+
+ $expected = <<<'TXT'
+ main
+ div
+ h1
+ p
+ small
+ TXT;
+
+ $this->html($html)->assertStructureLooksLike($expected);
+ }
+
+ public function testAssertLooksLike()
+ {
+ $html = <<<'HTML'
+
+
+
+ HTML;
+
+ $expected = <<<'TXT'
+ Foo
+ Bar Baz
+ TXT;
+
+ $this->html($html)->assertLooksLike($expected);
+ }
+
+ public function testElementInstance()
+ {
+ $this->assertInstanceOf(TestableHtmlElement::class, $this->exampleElement());
+ }
+
+ public function testElementTag()
+ {
+ $this->assertSame('div', $this->exampleElement()->tag);
+ }
+
+ public function testElementText()
+ {
+ $this->assertSame('Foo', $this->exampleElement()->text);
+ }
+
+ public function testElementHtml()
+ {
+ $this->assertSame('Foo
', $this->exampleElement()->html);
+ }
+
+ public function testElementId()
+ {
+ $this->assertSame('foo', $this->exampleElement()->id);
+
+ $this->assertNull($this->html('Foo
')->getRootElement()->id);
+ }
+
+ public function testElementClasses()
+ {
+ $this->assertSame([], $this->html('Foo
')->getRootElement()->classes);
+ $this->assertSame(['foo', 'bar'], $this->html('Foo
')->getRootElement()->classes);
+ }
+
+ public function testElementAttributes()
+ {
+ $this->assertSame([], $this->html('Foo
')->getRootElement()->attributes);
+
+ /** @noinspection HtmlUnknownAttribute */
+ $this->assertSame([
+ 'name' => 'test',
+ 'foo' => 'bar',
+ 'href' => 'https://example.com/',
+ ], $this->html('Foo
')->getRootElement()->attributes);
+ }
+
+ public function testElementNodes()
+ {
+ $this->assertNull($this->exampleElement()->nodes->first());
+ }
+
+ public function testElementNodesWithChild()
+ {
+ $child = $this->html('Bar
')->getRootElement()->nodes->first();
+
+ $this->assertInstanceOf(TestableHtmlElement::class, $child);
+ $this->assertSame('foo', $child->tag);
+ $this->assertSame('Bar', $child->text);
+ }
+
+ public function testElementNodesWithChildren()
+ {
+ $element = $this->html('BarBazFoo
')->getRootElement();
+
+ $this->assertCount(2, $element->nodes);
+ $this->assertSame('foo', $element->nodes->first()->tag);
+ $this->assertSame('bar', $element->nodes->last()->tag);
+
+ $this->assertCount(1, $element->nodes->last()->nodes);
+ $this->assertSame('small', $element->nodes->last()->nodes->first()->tag);
+
+ $this->assertSame('Foo', $element->nodes->last()->nodes->first()->text);
+ $this->assertNull($element->nodes->last()->nodes->first()->nodes->first());
+ }
+
+ public function testElementToArray()
+ {
+ $this->assertSame(
+ ['id' => 'foo', 'tag' => 'div', 'text' => 'Foo', 'classes' => ['bar']],
+ $this->exampleElement()->toArray()
+ );
+ }
+
+ public function testToArrayWithChildren()
+ {
+ $this->assertEquals(
+ ['tag' => 'div', 'nodes' => collect([$this->html('
')->getRootElement()->nodes->first()])],
+ $this->html('
')->getRootElement()->toArray()
+ );
+
+ $this->assertSame(
+ ['id', 'tag', 'text', 'classes', 'attributes', 'nodes'],
+ array_keys($this->html('Foo
')->getRootElement()->toArray())
+ );
+ }
+
+ public function testToArrayWithAttributes()
+ {
+ /** @noinspection HtmlUnknownAttribute */
+ $this->assertSame(
+ ['id' => 'id', 'tag' => 'div', 'text' => 'Bar', 'classes' => ['class'], 'attributes' => ['name' => 'name']],
+ $this->html('Bar
')->getRootElement()->toArray()
+ );
+ }
+
+ public function testElementAssertHasClass()
+ {
+ $this->html('Foo
')->getRootElement()->hasClass('foo');
+ }
+
+ public function testElementAssertDoesNotHaveClass()
+ {
+ $this->html('Foo
')->getRootElement()->doesNotHaveClass('bar');
+ }
+
+ protected function exampleElement(): TestableHtmlElement
+ {
+ return $this->html('Foo
')->getElementById('foo');
+ }
+}