From 81619a3c91d9e7b40c53139ba176751eaec580e4 Mon Sep 17 00:00:00 2001
From: ARCANEDEV <arcanedev.maroc@gmail.com>
Date: Tue, 19 Mar 2024 22:01:38 +0000
Subject: [PATCH] Updating the package

---
 composer.json                              |   1 -
 src/Contracts/Selectable.php               |   2 +-
 src/Elements/Button.php                    |   4 +-
 src/Elements/Concerns/HasChildElements.php |  10 +-
 src/Elements/Fieldset.php                  |   9 +
 src/Elements/File.php                      |   3 +-
 src/Elements/HtmlElement.php               |  36 +-
 src/Elements/I.php                         |   3 +
 src/Elements/ListElement.php               |   9 +-
 src/Elements/Optgroup.php                  |   9 +
 src/Elements/Option.php                    |  13 +-
 src/Elements/P.php                         |  18 +
 src/Elements/Select.php                    |   6 +-
 src/Elements/Textarea.php                  |   6 -
 src/Entities/ChildrenCollection.php        |   2 +-
 src/Html.php                               |   9 +
 tests/Elements/ATest.php                   |   2 +-
 tests/Elements/ButtonTest.php              |  18 +
 tests/Elements/DivTest.php                 | 404 +++++++++++++++++++++
 tests/Elements/FieldsetTest.php            |   9 +
 tests/Elements/HtmlElementTest.php         |  11 +
 tests/Elements/InputTest.php               |   9 +
 tests/Elements/OptgroupTest.php            |   9 +
 tests/Elements/OptionTest.php              |   7 +-
 tests/Elements/PTest.php                   |  30 ++
 tests/Elements/TextareaTest.php            |  90 +++++
 tests/Entities/AttributesTest.php          |  47 ++-
 27 files changed, 709 insertions(+), 67 deletions(-)
 create mode 100644 src/Elements/P.php
 create mode 100644 tests/Elements/PTest.php

diff --git a/composer.json b/composer.json
index e75fe08..fe240fb 100644
--- a/composer.json
+++ b/composer.json
@@ -35,7 +35,6 @@
     "scripts": {
         "test": "phpunit --colors=always",
         "test:dox": "phpunit --testdox --colors=always",
-        "test:cov": "phpunit --coverage-html",
         "test:ci": "phpunit --coverage-text",
         "cs:fix": "pint -v",
         "cs:test": "pint --test -v"
diff --git a/src/Contracts/Selectable.php b/src/Contracts/Selectable.php
index f545f32..48d03be 100644
--- a/src/Contracts/Selectable.php
+++ b/src/Contracts/Selectable.php
@@ -19,7 +19,7 @@ interface Selectable
     /**
      * Add the selected attribute.
      */
-    public function selected(bool $selected = true): static;
+    public function selected(): static;
 
     /**
      * Add the selected if it fulfill the condition.
diff --git a/src/Elements/Button.php b/src/Elements/Button.php
index 29acb28..adf8010 100644
--- a/src/Elements/Button.php
+++ b/src/Elements/Button.php
@@ -4,6 +4,7 @@
 
 namespace Arcanedev\Html\Elements;
 
+use Arcanedev\Html\Elements\Concerns\HasDisabledAttribute;
 use Arcanedev\Html\Elements\Concerns\HasNameAttribute;
 use Arcanedev\Html\Elements\Concerns\HasTypeAttribute;
 use Arcanedev\Html\Elements\Concerns\HasValueAttribute;
@@ -20,10 +21,9 @@ class Button extends HtmlElement
      | -----------------------------------------------------------------
      */
 
+    use HasDisabledAttribute;
     use HasNameAttribute;
-
     use HasTypeAttribute;
-
     use HasValueAttribute;
 
     /* -----------------------------------------------------------------
diff --git a/src/Elements/Concerns/HasChildElements.php b/src/Elements/Concerns/HasChildElements.php
index 0d0c468..2efdf0f 100644
--- a/src/Elements/Concerns/HasChildElements.php
+++ b/src/Elements/Concerns/HasChildElements.php
@@ -70,7 +70,7 @@ public function initChildren(): static
      *
      * @return $this
      */
-    public function children(mixed $children, ?Closure $mapper = null): static
+    public function children(mixed $children, Closure|array|null $mapper = null): static
     {
         return $this->addChild($children, $mapper);
     }
@@ -80,7 +80,7 @@ public function children(mixed $children, ?Closure $mapper = null): static
      *
      * @return $this
      */
-    public function addChild(mixed $child, ?Closure $mapper = null): static
+    public function addChild(mixed $child, Closure|array|null $mapper = null): static
     {
         if ($child === null) {
             return $this;
@@ -98,7 +98,7 @@ public function addChild(mixed $child, ?Closure $mapper = null): static
      *
      * @return $this
      */
-    public function setNewChildren(mixed $children, ?Closure $mapper = null): static
+    public function setNewChildren(mixed $children, Closure|array|null $mapper = null): static
     {
         return tap(clone $this)
             ->initChildren()
@@ -110,7 +110,7 @@ public function setNewChildren(mixed $children, ?Closure $mapper = null): static
      *
      * @return $this
      */
-    public function prependChild(mixed $children, ?Closure $mapper = null): static
+    public function prependChild(mixed $children, Closure|array|null $mapper = null): static
     {
         return $this->prependChildren($children, $mapper);
     }
@@ -120,7 +120,7 @@ public function prependChild(mixed $children, ?Closure $mapper = null): static
      *
      * @return $this
      */
-    public function prependChildren(mixed $children, ?Closure $mapper = null): static
+    public function prependChildren(mixed $children, Closure|array|null $mapper = null): static
     {
         return tap(clone $this, function (HtmlElement $elt) use ($children, $mapper): void {
             $elt->getChildren()
diff --git a/src/Elements/Fieldset.php b/src/Elements/Fieldset.php
index 6f149f5..b336e6b 100644
--- a/src/Elements/Fieldset.php
+++ b/src/Elements/Fieldset.php
@@ -4,6 +4,8 @@
 
 namespace Arcanedev\Html\Elements;
 
+use Arcanedev\Html\Elements\Concerns\HasDisabledAttribute;
+
 /**
  * Class     Fieldset
  *
@@ -11,6 +13,13 @@
  */
 class Fieldset extends HtmlElement
 {
+    /* -----------------------------------------------------------------
+     |  Properties
+     | -----------------------------------------------------------------
+     */
+
+    use HasDisabledAttribute;
+
     /* -----------------------------------------------------------------
      |  Properties
      | -----------------------------------------------------------------
diff --git a/src/Elements/File.php b/src/Elements/File.php
index c2f750d..c5c0f70 100644
--- a/src/Elements/File.php
+++ b/src/Elements/File.php
@@ -21,10 +21,9 @@ class File extends HtmlElement
      */
 
     use HasAutofocusAttribute;
-
     use HasNameAttribute;
-
     use HasRequiredAttribute;
+
     /* -----------------------------------------------------------------
      |  Constants
      | -----------------------------------------------------------------
diff --git a/src/Elements/HtmlElement.php b/src/Elements/HtmlElement.php
index cc09493..2870e1f 100644
--- a/src/Elements/HtmlElement.php
+++ b/src/Elements/HtmlElement.php
@@ -5,13 +5,11 @@
 namespace Arcanedev\Html\Elements;
 
 use Arcanedev\Html\Contracts\Elements\HtmlElement as HtmlElementContract;
+use Arcanedev\Html\Elements\Concerns\{HasAttributes, HasChildElements, HasConditionalMethods};
 use Arcanedev\Html\Entities\Attributes\ClassAttribute;
 use Arcanedev\Html\Exceptions\InvalidHtmlException;
 use Arcanedev\Html\Exceptions\MissingTagException;
-use Illuminate\Support\Collection;
-use Illuminate\Support\HtmlString;
-use Illuminate\Support\Str;
-use Illuminate\Support\Traits\Macroable;
+use Illuminate\Support\{Collection, HtmlString, Str, Traits\Macroable};
 
 /**
  * Class     HtmlElement
@@ -29,12 +27,12 @@ class HtmlElement implements HtmlElementContract
      | -----------------------------------------------------------------
      */
 
-    use Concerns\HasAttributes,
-        Concerns\HasChildElements,
-        Concerns\HasConditionalMethods,
-        Macroable {
-            __call as __callMacro;
-        }
+    use HasAttributes;
+    use HasChildElements;
+    use HasConditionalMethods;
+    use Macroable {
+        __call as __callMacro;
+    }
 
     /* -----------------------------------------------------------------
      |  Properties
@@ -200,11 +198,9 @@ public function text(mixed $text, bool $doubleEncode = true): static
     }
 
     /**
-     * Add an html child/children.
+     * Add a html child/children.
      *
      * @return $this
-     *
-     * @throws InvalidHtmlException
      */
     public function html(mixed $html): static
     {
@@ -295,11 +291,21 @@ protected function setTag(string $tag): static
      * @throws MissingTagException
      */
     protected function getTag(): string
+    {
+        $this->ensureHasTag();
+
+        return $this->tag;
+    }
+
+    /**
+     * Ensure the tag property is defined.
+     *
+     * @throws MissingTagException
+     */
+    protected function ensureHasTag(): void
     {
         if (empty($this->tag)) {
             throw MissingTagException::onClass(static::class);
         }
-
-        return $this->tag;
     }
 }
diff --git a/src/Elements/I.php b/src/Elements/I.php
index 3cf3ff7..872870b 100644
--- a/src/Elements/I.php
+++ b/src/Elements/I.php
@@ -16,5 +16,8 @@ class I extends HtmlElement
      | -----------------------------------------------------------------
      */
 
+    /**
+     * The tag type.
+     */
     protected string $tag = 'i';
 }
diff --git a/src/Elements/ListElement.php b/src/Elements/ListElement.php
index db08e54..c79fa68 100644
--- a/src/Elements/ListElement.php
+++ b/src/Elements/ListElement.php
@@ -11,15 +11,16 @@
  */
 abstract class ListElement extends HtmlElement
 {
-    /**
-     * Make an item.
-     */
-    abstract protected function makeItem(mixed $value, array $attributes): HtmlElement;
     /* -----------------------------------------------------------------
      |  Main Methods
      | -----------------------------------------------------------------
      */
 
+    /**
+     * Make an item.
+     */
+    abstract protected function makeItem(mixed $value, array $attributes): HtmlElement;
+
     /**
      * Add an item.
      *
diff --git a/src/Elements/Optgroup.php b/src/Elements/Optgroup.php
index e1108d8..77dd48d 100644
--- a/src/Elements/Optgroup.php
+++ b/src/Elements/Optgroup.php
@@ -4,6 +4,8 @@
 
 namespace Arcanedev\Html\Elements;
 
+use Arcanedev\Html\Elements\Concerns\HasDisabledAttribute;
+
 /**
  * Class     Optgroup
  *
@@ -11,6 +13,13 @@
  */
 class Optgroup extends HtmlElement
 {
+    /* -----------------------------------------------------------------
+     |  Traits
+     | -----------------------------------------------------------------
+     */
+
+    use HasDisabledAttribute;
+
     /* -----------------------------------------------------------------
      |  Properties
      | -----------------------------------------------------------------
diff --git a/src/Elements/Option.php b/src/Elements/Option.php
index 56ee7ff..46bcf80 100644
--- a/src/Elements/Option.php
+++ b/src/Elements/Option.php
@@ -22,7 +22,6 @@ class Option extends HtmlElement implements Selectable
      */
 
     use HasDisabledAttribute;
-
     use HasValueAttribute;
 
     /* -----------------------------------------------------------------
@@ -44,17 +43,17 @@ class Option extends HtmlElement implements Selectable
      */
     public function selectedIf(bool $condition): static
     {
-        return $condition ? $this->selected() : $this->unselected();
+        return $condition
+            ? $this->attribute('selected')
+            : $this->forgetAttribute('selected');
     }
 
     /**
      * Add the selected attribute.
      */
-    public function selected(bool $selected = true): static
+    public function selected(): static
     {
-        return $selected
-            ? $this->attribute('selected')
-            : $this->forgetAttribute('selected');
+        return $this->selectedIf(true);
     }
 
     /**
@@ -62,6 +61,6 @@ public function selected(bool $selected = true): static
      */
     public function unselected(): static
     {
-        return $this->selected(false);
+        return $this->selectedIf(false);
     }
 }
diff --git a/src/Elements/P.php b/src/Elements/P.php
new file mode 100644
index 0000000..fec4095
--- /dev/null
+++ b/src/Elements/P.php
@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Arcanedev\Html\Elements;
+
+class P extends HtmlElement
+{
+    /* -----------------------------------------------------------------
+     |  Properties
+     | -----------------------------------------------------------------
+     */
+
+    /**
+     * The tag type.
+     */
+    protected string $tag = 'p';
+}
diff --git a/src/Elements/Select.php b/src/Elements/Select.php
index aa9fb65..efb1bab 100644
--- a/src/Elements/Select.php
+++ b/src/Elements/Select.php
@@ -25,13 +25,9 @@ class Select extends HtmlElement
      */
 
     use HasAutofocusAttribute;
-
     use HasDisabledAttribute;
-
     use HasNameAttribute;
-
     use HasReadonlyAttribute;
-
     use HasRequiredAttribute;
 
     /* -----------------------------------------------------------------
@@ -76,7 +72,7 @@ public function options(iterable $options, array $attributes = [], array $groupA
     {
         return $this->children(
             $options,
-            fn($text, $value) => is_array($text)
+            fn($text, $value) => is_array($text) || $text instanceof Collection
             ? $this->makeOptionsGroup($value, $text, $attributes, $groupAttributes[$value] ?? [])
             : $this->makeOption($value, $text, $attributes[$value] ?? [])
         );
diff --git a/src/Elements/Textarea.php b/src/Elements/Textarea.php
index d8f0eab..4105716 100644
--- a/src/Elements/Textarea.php
+++ b/src/Elements/Textarea.php
@@ -25,17 +25,11 @@ class Textarea extends HtmlElement
      */
 
     use HasAutofocusAttribute;
-
     use HasDisabledAttribute;
-
     use HasMinMaxLengthAttributes;
-
     use HasNameAttribute;
-
     use HasPlaceholderAttribute;
-
     use HasReadonlyAttribute;
-
     use HasRequiredAttribute;
 
     /* -----------------------------------------------------------------
diff --git a/src/Entities/ChildrenCollection.php b/src/Entities/ChildrenCollection.php
index a69bbdc..ab970ca 100644
--- a/src/Entities/ChildrenCollection.php
+++ b/src/Entities/ChildrenCollection.php
@@ -30,7 +30,7 @@ class ChildrenCollection extends Collection implements Renderable
      *
      * @throws InvalidChildException
      */
-    public static function parse(mixed $children, ?Closure $mapper = null): static
+    public static function parse(mixed $children, Closure|array|null $mapper = null): static
     {
         return static::make($children)
             ->unless($mapper === null, fn(ChildrenCollection $items) => $items->map($mapper))
diff --git a/src/Html.php b/src/Html.php
index 1f8d5e3..f66e822 100644
--- a/src/Html.php
+++ b/src/Html.php
@@ -20,6 +20,7 @@
 use Arcanedev\Html\Elements\Legend;
 use Arcanedev\Html\Elements\Ol;
 use Arcanedev\Html\Elements\Option;
+use Arcanedev\Html\Elements\P;
 use Arcanedev\Html\Elements\Select;
 use Arcanedev\Html\Elements\Span;
 use Arcanedev\Html\Elements\Textarea;
@@ -277,6 +278,14 @@ public function option(?string $text = null, mixed $value = null, bool $selected
             ->selectedIf($selected);
     }
 
+    /**
+     * Make a paragraph tag.
+     */
+    public function p(HtmlElement|string|null $content = null): P
+    {
+        return P::make()->html($content);
+    }
+
     /**
      * Make a password input.
      */
diff --git a/tests/Elements/ATest.php b/tests/Elements/ATest.php
index 3c08382..1d031cf 100644
--- a/tests/Elements/ATest.php
+++ b/tests/Elements/ATest.php
@@ -24,7 +24,7 @@ public function it_can_create(): void
     {
         static::assertHtmlStringEqualsHtmlString(
             '<a></a>',
-            A::make()->toHtml()
+            A::make()
         );
     }
 
diff --git a/tests/Elements/ButtonTest.php b/tests/Elements/ButtonTest.php
index 9bc7069..87e88ec 100644
--- a/tests/Elements/ButtonTest.php
+++ b/tests/Elements/ButtonTest.php
@@ -28,6 +28,15 @@ public function it_can_create(): void
         );
     }
 
+    #[Test]
+    public function it_can_create_a_button_with_a_value(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<button value="1"></button>',
+            Button::make()->value(1)
+        );
+    }
+
     #[Test]
     public function it_can_create_with_custom_type_and_value(): void
     {
@@ -84,4 +93,13 @@ public function it_can_create_with_a_specific_type(): void
             Button::make()->reset()->html('Reset the form')
         );
     }
+
+    #[Test]
+    public function it_can_disable_a_button(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<button disabled></button>',
+            Button::make()->disabled()
+        );
+    }
 }
diff --git a/tests/Elements/DivTest.php b/tests/Elements/DivTest.php
index 3790ccb..5967141 100644
--- a/tests/Elements/DivTest.php
+++ b/tests/Elements/DivTest.php
@@ -5,6 +5,13 @@
 namespace Arcanedev\Html\Tests\Elements;
 
 use Arcanedev\Html\Elements\Div;
+use Arcanedev\Html\Elements\HtmlElement;
+use Arcanedev\Html\Entities\Attributes\MiscAttribute;
+use Arcanedev\Html\Exceptions\InvalidChildException;
+use Arcanedev\Html\Exceptions\InvalidHtmlException;
+use BadMethodCallException;
+use Illuminate\Support\Collection;
+use Illuminate\Support\HtmlString;
 use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\Attributes\Test;
 
@@ -36,6 +43,7 @@ public static function getCustomStylesDP(): array
             ],
         ];
     }
+
     /* -----------------------------------------------------------------
      |  Tests
      | -----------------------------------------------------------------
@@ -50,6 +58,402 @@ public function it_can_create(): void
         );
     }
 
+    #[Test]
+    public function it_can_set_an_attribute_with_set_attribute(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div foo="bar"></div>',
+            Div::make()->attribute('foo', 'bar')->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_set_an_attribute_to_null(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div foo=""></div>',
+            Div::make()->attribute('foo')->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_set_an_attribute_with_attribute(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div foo="bar"></div>',
+            Div::make()->attribute('foo', 'bar')->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_set_an_attribute_with_attribute_if(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div foo="bar"></div>',
+            Div::make()->attributeIf(true, 'foo', 'bar')->attributeIf(false, 'bar', 'baz')->render()
+        );
+
+        static::assertHtmlStringEqualsHtmlString(
+            '<div foo="bar"></div>',
+            Div::make()->attributeUnless(false, 'foo', 'bar')->attributeUnless(true, 'bar', 'baz')->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_set_an_class_with_class_if(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div class="bar"></div>',
+            Div::make()->classIf(true, 'bar')->classIf(false, 'baz')->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_not_accept_any_if_method(): void
+    {
+        $this->expectException(BadMethodCallException::class);
+
+        Div::make()->barIf(true, 'bar')->render();
+    }
+
+    #[Test]
+    public function it_can_forget_an_attribute(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div></div>',
+            Div::make()->attribute('foo', 'bar')->forgetAttribute('foo')->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_get_an_attribute(): void
+    {
+        $element = Div::make()->attribute('foo', 'bar');
+
+        static::assertTrue($element->hasAttribute('foo'));
+
+        $attribute = $element->getAttribute('foo');
+
+        static::assertInstanceOf(MiscAttribute::class, $attribute);
+        static::assertSame('foo', $attribute->name());
+        static::assertSame('bar', $attribute->value());
+    }
+
+    #[Test]
+    public function it_must_return_null_if_an_attribute_does_not_exists(): void
+    {
+        $element = Div::make();
+
+        static::assertFalse($element->hasAttribute('foo'));
+        static::assertNull($element->getAttribute('foo'));
+    }
+
+    #[Test]
+    public function it_can_set_an_id(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div id="main"></div>',
+            Div::make()->id('main')->render()
+        );
+    }
+
+    #[Test]
+    public function multiple_attributes_can_be_set_with_attributes(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div foo bar="baz"></div>',
+            Div::make()->attributes(['foo', 'bar' => 'baz'])->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_add_a_class_with_add_class(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div class="foo"></div>',
+            Div::make()->class('foo')->render()
+        );
+
+        static::assertHtmlStringEqualsHtmlString(
+            '<div class="foo bar"></div>',
+            Div::make()->class(['foo', 'bar'])->render()
+        );
+
+        static::assertHtmlStringEqualsHtmlString(
+            '<div class="foo"></div>',
+            Div::make()->class(['foo', 'bar' => false])->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_add_a_class_with_class(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div class="foo"></div>',
+            Div::make()->class('foo')->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_set_style_from_a_string(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div style="color: red"></div>',
+            Div::make()->style('color: red')->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_set_style_from_an_array(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div style="color: red"></div>',
+            Div::make()->style(['color' => 'red'])->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_set_text(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div>Hi &amp; Bye</div>',
+            Div::make()->text('Hi & Bye')->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_set_html(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><span>Yo</span></div>',
+            Div::make()->html('<span>Yo</span>')->render()
+        );
+    }
+
+    #[Test]
+    public function it_can_set_html_from_htmlstring(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><span>Yo</span></div>',
+            Div::make()->html(new HtmlString('<span>Yo</span>'))->render()
+        );
+    }
+
+    #[Test]
+    public function it_cant_set_html_if_its_not_an_html_element(): void
+    {
+        $this->expectException(InvalidChildException::class);
+
+        Div::make()->html(true)->render();
+    }
+
+    #[Test]
+    public function setting_text_overwrites_existing_children(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div>Hi</div>',
+            Div::make()->addChild(Div::make())->text('Hi')->render()
+        );
+    }
+
+    #[Test]
+    public function it_cant_add_child_if_its_not_an_html_element_or_a_string(): void
+    {
+        $this->expectException(InvalidChildException::class);
+
+        Div::make()->addChild(true)->render();
+    }
+
+    #[Test]
+    public function it_cant_set_text_if_its_a_void_element(): void
+    {
+        $this->expectException(InvalidHtmlException::class);
+        $this->expectExceptionMessage("Can't set inner contents on `img` because it's a void element");
+
+        $img = new class () extends HtmlElement {
+            protected string $tag = 'img';
+        };
+
+        $img->text('Hi');
+    }
+
+    #[Test]
+    public function it_can_add_a_child_from_a_string(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div>Hello</div>',
+            Div::make()->addChild('Hello')
+        );
+    }
+
+    #[Test]
+    public function it_can_add_a_child_from_an_element(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>Hello</div></div>',
+            Div::make()->addChild(Div::make()->text('Hello'))
+        );
+    }
+
+    #[Test]
+    public function it_can_add_children_from_an_array_of_strings(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div>Helloworld</div>',
+            Div::make()->addChild(['Hello', 'world'])
+        );
+    }
+
+    #[Test]
+    public function it_can_add_children_from_an_array_of_elements(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>Hello</div><div>World</div></div>',
+            Div::make()->children([Div::make()->text('Hello'), Div::make()->text('World')])
+        );
+    }
+
+    #[Test]
+    public function it_can_add_children_from_an_iterable(): void
+    {
+        $children = Collection::make([Div::make()->text('Hello'), Div::make()->text('World')]);
+
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>Hello</div><div>World</div></div>',
+            Div::make()->addChild($children)
+        );
+    }
+
+    #[Test]
+    public function it_doesnt_add_a_child_if_the_child_is_null(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div></div>',
+            Div::make()->addChild(null)
+        );
+    }
+
+    #[Test]
+    public function it_can_transform_children_when_they_are_added(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>Hello</div><div>World</div></div>',
+            Div::make()->children(['Hello', 'World'], [$this, 'wrapInDiv'])
+        );
+
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>Hello</div><div>World</div></div>',
+            Div::make()->children(['Hello', 'World'], [$this, 'wrapInDiv'])
+        );
+
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>Hello</div><div>World</div></div>',
+            Div::make()->children(['Hello', 'World'], [$this, 'wrapInDiv'])
+        );
+
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>Hello</div><div>World</div></div>',
+            Div::make()->children(['Hello', 'World'], [$this, 'wrapInDiv'])
+        );
+    }
+
+    #[Test]
+    public function it_can_add_a_child_with_add_child(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>Hello</div></div>',
+            Div::make()->addChild(Div::make()->text('Hello'))
+        );
+    }
+
+    #[Test]
+    public function it_can_add_a_child_with_child(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>Hello</div></div>',
+            Div::make()->addChild(Div::make()->text('Hello'))
+        );
+    }
+
+    #[Test]
+    public function it_can_add_children_with_children(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>Hello</div></div>',
+            Div::make()->children(Div::make()->text('Hello'))
+        );
+    }
+
+    #[Test]
+    public function it_can_prepend_children_with_prepend_children(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>World</div><div>Hello</div></div>',
+            Div::make()
+                ->children(Div::make()->text('Hello'))
+                ->prependChildren(Div::make()->text('World'))
+        );
+    }
+
+    #[Test]
+    public function it_can_prepend_children_with_prepend_child(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>World</div><div>Hello</div></div>',
+            Div::make()
+                ->addChild(Div::make()->text('Hello'))
+                ->prependChild(Div::make()->text('World'))
+        );
+    }
+
+    #[Test]
+    public function it_can_transform_children_when_they_are_prepended(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>World</div><div>Hello</div></div>',
+            Div::make()
+                ->addChild(Div::make()->text('Hello'))
+                ->prependChildren(['World'], [$this, 'wrapInDiv'])
+        );
+
+        static::assertHtmlStringEqualsHtmlString(
+            '<div><div>World</div><div>Hello</div></div>',
+            Div::make()
+                ->addChild(Div::make()->text('Hello'))
+                ->prependChild('World', [$this, 'wrapInDiv'])
+        );
+    }
+
+    #[Test]
+    public function it_can_conditionally_transform_an_element(): void
+    {
+        $div = Div::make()
+            ->if(true, fn(Div $div) => $div->class('foo'))
+            ->if(false, fn(Div $div) => $div->class('bar'));
+
+        static::assertHtmlStringEqualsHtmlString('<div class="foo"></div>', $div);
+
+        $div = Div::make()
+            ->unless(false, fn(Div $div) => $div->class('foo'))
+            ->unless(true, fn(Div $div) => $div->class('bar'));
+
+        static::assertHtmlStringEqualsHtmlString('<div class="foo"></div>', $div);
+    }
+
+    public function wrapInDiv(string $text): Div
+    {
+        return Div::make()->text($text);
+    }
+
+    #[Test]
+    public function it_can_set_a_data_attribute(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<div data-foo="bar"></div>',
+            Div::make()->data('foo', 'bar')->render()
+        );
+    }
+
     #[Test]
     #[DataProvider('getCustomStylesDP')]
     public function it_can_create_with_custom_styles(array $styles, string $expected): void
diff --git a/tests/Elements/FieldsetTest.php b/tests/Elements/FieldsetTest.php
index 057e6ba..177f3cf 100644
--- a/tests/Elements/FieldsetTest.php
+++ b/tests/Elements/FieldsetTest.php
@@ -36,4 +36,13 @@ public function it_can_add_a_legend_to_the_fieldset(): void
             Fieldset::make()->legend('Legend')
         );
     }
+
+    #[Test]
+    public function it_can_disable_a_fieldset(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<fieldset disabled></fieldset>',
+            Fieldset::make()->disabled()
+        );
+    }
 }
diff --git a/tests/Elements/HtmlElementTest.php b/tests/Elements/HtmlElementTest.php
index c67a8a2..089a2c0 100644
--- a/tests/Elements/HtmlElementTest.php
+++ b/tests/Elements/HtmlElementTest.php
@@ -5,6 +5,7 @@
 namespace Arcanedev\Html\Tests\Elements;
 
 use Arcanedev\Html\Elements\HtmlElement;
+use Arcanedev\Html\Exceptions\MissingTagException;
 use PHPUnit\Framework\Attributes\Test;
 
 /**
@@ -19,6 +20,16 @@ class HtmlElementTest extends TestCase
      | -----------------------------------------------------------------
      */
 
+    #[Test]
+    public function it_cant_be_instantiated_without_a_tag_name_on_the_class(): void
+    {
+        static::expectException(MissingTagException::class);
+
+        $element = new class () extends HtmlElement {};
+
+        $element->render();
+    }
+
     #[Test]
     public function it_can_register_a_macro(): void
     {
diff --git a/tests/Elements/InputTest.php b/tests/Elements/InputTest.php
index 462e131..34e2cea 100644
--- a/tests/Elements/InputTest.php
+++ b/tests/Elements/InputTest.php
@@ -146,6 +146,15 @@ public function it_can_uncheck(): void
         );
     }
 
+    #[Test]
+    public function it_can_disable_an_input(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<input type="checkbox" disabled>',
+            Input::make()->type('checkbox')->disabled()
+        );
+    }
+
     #[Test]
     public function it_can_create_with_builder(): void
     {
diff --git a/tests/Elements/OptgroupTest.php b/tests/Elements/OptgroupTest.php
index 4861e76..6cf0408 100644
--- a/tests/Elements/OptgroupTest.php
+++ b/tests/Elements/OptgroupTest.php
@@ -36,4 +36,13 @@ public function it_can_create_with_a_label(): void
             Optgroup::make()->label('Cats')
         );
     }
+
+    #[Test]
+    public function it_can_disable_an_optgroup(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<optgroup disabled></optgroup>',
+            Optgroup::make()->disabled()
+        );
+    }
 }
diff --git a/tests/Elements/OptionTest.php b/tests/Elements/OptionTest.php
index 93500d5..2d5d6c2 100644
--- a/tests/Elements/OptionTest.php
+++ b/tests/Elements/OptionTest.php
@@ -24,7 +24,7 @@ public function it_can_render_an_empty_version_itself(): void
     {
         static::assertHtmlStringEqualsHtmlString(
             '<option></option>',
-            Option::make()->render()
+            Option::make()
         );
     }
 
@@ -44,6 +44,11 @@ public function it_can_render_itself_in_a_selected_state(): void
             '<option selected value="0">Choose...</option>',
             Option::make()->value('0')->text('Choose...')->selected()
         );
+
+        static::assertHtmlStringEqualsHtmlString(
+            '<option selected value="0">Choose...</option>',
+            Option::make()->value('0')->text('Choose...')->selected()
+        );
     }
 
     #[Test]
diff --git a/tests/Elements/PTest.php b/tests/Elements/PTest.php
new file mode 100644
index 0000000..7760ff5
--- /dev/null
+++ b/tests/Elements/PTest.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Arcanedev\Html\Tests\Elements;
+
+use Arcanedev\Html\Elements\P;
+use PHPUnit\Framework\Attributes\Test;
+
+/**
+ * Class     PTest
+ *
+ * @author   ARCANEDEV <arcanedev.maroc@gmail.com>
+ */
+class PTest extends TestCase
+{
+    /* -----------------------------------------------------------------
+     |  Tests
+     | -----------------------------------------------------------------
+     */
+
+    #[Test]
+    public function it_can_create_an_i_element(): void
+    {
+        static::assertHtmlStringEqualsHtmlString(
+            '<p></p>',
+            P::make()
+        );
+    }
+}
diff --git a/tests/Elements/TextareaTest.php b/tests/Elements/TextareaTest.php
index 70f0590..b76d1e9 100644
--- a/tests/Elements/TextareaTest.php
+++ b/tests/Elements/TextareaTest.php
@@ -81,4 +81,94 @@ public function it_can_set_the_size(): void
             Textarea::make()->value('My epic content')->size('60x15')
         );
     }
+
+    #[Test]
+    public function it_can_create_a_textarea_that_is_required_when_passing_true(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<textarea required>My epic</textarea>',
+            Textarea::make()->value('My epic')->required(true)
+        );
+    }
+
+    #[Test]
+    public function it_wont_create_a_textarea_that_is_required_when_passing_false(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<textarea>My epic</textarea>',
+            Textarea::make()->value('My epic')->required(false)
+        );
+    }
+
+    #[Test]
+    public function it_can_create_a_disabled_textarea(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<textarea disabled>My epic</textarea>',
+            Textarea::make()->value('My epic')->disabled()
+        );
+    }
+
+    #[Test]
+    public function it_can_create_a_textarea_that_is_disabled_when_passing_true(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<textarea disabled>My epic</textarea>',
+            Textarea::make()->value('My epic')->disabled(true)
+        );
+    }
+
+    #[Test]
+    public function it_wont_create_a_textarea_that_is_disabled_when_passing_false(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<textarea>My epic</textarea>',
+            Textarea::make()->value('My epic')->disabled(false)
+        );
+    }
+
+    #[Test]
+    public function it_can_create_a_readonly_textarea(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<textarea readonly>My epic</textarea>',
+            Textarea::make()->value('My epic')->isReadonly()
+        );
+    }
+
+    #[Test]
+    public function it_can_create_a_textarea_that_is_readonly_when_passing_true(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<textarea readonly>My epic</textarea>',
+            Textarea::make()->value('My epic')->isReadonly(true)
+        );
+    }
+
+    #[Test]
+    public function it_wont_create_a_textarea_that_is_readonly_when_passing_false(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<textarea>My epic</textarea>',
+            Textarea::make()->value('My epic')->isReadonly(false)
+        );
+    }
+
+    #[Test]
+    public function it_can_create_a_textarea_with_maxlength(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<textarea maxlength="25">My epic</textarea>',
+            Textarea::make()->value('My epic')->maxlength(25)
+        );
+    }
+
+    #[Test]
+    public function it_can_create_a_textarea_with_minlength(): void
+    {
+        $this->assertHtmlStringEqualsHtmlString(
+            '<textarea minlength="25">My epic</textarea>',
+            Textarea::make()->value('My epic')->minlength(25)
+        );
+    }
 }
diff --git a/tests/Entities/AttributesTest.php b/tests/Entities/AttributesTest.php
index 74a0b30..7e4ffd0 100644
--- a/tests/Entities/AttributesTest.php
+++ b/tests/Entities/AttributesTest.php
@@ -35,7 +35,10 @@ public function it_accepts_classes_as_strings(): void
     {
         $attributes = (new Attributes())->addClass('foo bar');
 
-        static::assertEquals(['class' => 'foo bar'], $attributes->toArray());
+        $actual = $attributes->toArray();
+
+        static::assertArrayHasKey('class', $actual);
+        static::assertEquals(['class' => 'foo bar'], $actual);
     }
 
     #[Test]
@@ -43,7 +46,10 @@ public function it_accepts_classes_as_an_array(): void
     {
         $attributes = (new Attributes())->addClass(['foo', 'bar']);
 
-        static::assertEquals(['class' => 'foo bar'], $attributes->toArray());
+        $actual = $attributes->toArray();
+
+        static::assertArrayHasKey('class', $actual);
+        static::assertEquals(['class' => 'foo bar'], $actual);
     }
 
     #[Test]
@@ -54,7 +60,10 @@ public function it_can_add_classes_conditionally_with_an_associative_array(): vo
             'bar' => false,
         ]);
 
-        static::assertEquals(['class' => 'foo'], $attributes->toArray());
+        $actual = $attributes->toArray();
+
+        static::assertArrayHasKey('class', $actual);
+        static::assertEquals(['class' => 'foo'], $actual);
     }
 
     #[Test]
@@ -66,7 +75,10 @@ public function it_can_simultaneously_add_classes_conditionally_and_non_conditio
             'baz',
         ]);
 
-        static::assertEquals(['class' => 'foo baz'], $attributes->toArray());
+        $actual = $attributes->toArray();
+
+        static::assertArrayHasKey('class', $actual);
+        static::assertEquals(['class' => 'foo baz'], $actual);
     }
 
     #[Test]
@@ -76,10 +88,11 @@ public function it_accepts_attributes(): void
         $attributes->set('href', '#');
         $attributes->set('class', 'foobar');
 
-        static::assertEquals(
-            ['href' => '#', 'class' => 'foobar'],
-            $attributes->toArray()
-        );
+        $actual = $attributes->toArray();
+
+        static::assertArrayHasKey('href', $actual);
+        static::assertArrayHasKey('class', $actual);
+        static::assertEquals(['href' => '#', 'class' => 'foobar'], $actual);
     }
 
     #[Test]
@@ -87,10 +100,10 @@ public function it_accepts_attributes_without_values(): void
     {
         $attributes = (new Attributes())->set('required');
 
-        static::assertEquals(
-            ['required' => null],
-            $attributes->toArray()
-        );
+        $actual = $attributes->toArray();
+
+        static::assertArrayHasKey('required', $actual);
+        static::assertEquals(['required' => null], $actual);
     }
 
     #[Test]
@@ -159,10 +172,12 @@ public function it_accepts_multiple_attributes(): void
             'required',
         ]);
 
-        static::assertEquals(
-            ['name' => 'email', 'class' => 'foobar', 'required' => null],
-            $attributes->toArray()
-        );
+        $actual = $attributes->toArray();
+
+        static::assertArrayHasKey('name', $actual);
+        static::assertArrayHasKey('class', $actual);
+        static::assertArrayHasKey('required', $actual);
+        static::assertEquals(['name' => 'email', 'class' => 'foobar', 'required' => null], $actual);
     }
 
     #[Test]