From 0312ed1af25ce80e8e3d0fe9a1abfa89a9077992 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Fri, 10 Jan 2025 19:27:22 -0300 Subject: [PATCH] Add support for `activateItems()` and `currentPath()` to `Nav` widget. --- src/Dropdown.php | 48 ++++- src/DropdownItem.php | 42 +++- src/Nav.php | 198 ++++++++++++++--- src/NavLink.php | 173 ++++++++------- tests/DropdownItemTest.php | 34 +-- tests/NavLinkTest.php | 110 +--------- tests/NavTest.php | 432 ++++++++++++++++++++++++++++++------- 7 files changed, 714 insertions(+), 323 deletions(-) diff --git a/src/Dropdown.php b/src/Dropdown.php index aeb6a626..b4dd1f88 100644 --- a/src/Dropdown.php +++ b/src/Dropdown.php @@ -75,7 +75,7 @@ final class Dropdown extends \Yiisoft\Widget\Widget public function addAttributes(array $values): self { $new = clone $this; - $new->attributes = array_merge($this->attributes, $values); + $new->attributes = [...$this->attributes, $values]; return $new; } @@ -100,7 +100,7 @@ public function addAttributes(array $values): self public function addClass(BackedEnum|string|null ...$value): self { $new = clone $this; - $new->cssClasses = array_merge($new->cssClasses, $value); + $new->cssClasses = [...$this->cssClasses, ...$value]; return $new; } @@ -151,7 +151,7 @@ public function alignment(DropdownAlignment|null ...$value): self public function addToggleAttributes(array $values): self { $new = clone $this; - $new->toggleAttributes = array_merge($this->toggleAttributes, $values); + $new->toggleAttributes = [...$this->toggleAttributes, $values]; return $new; } @@ -254,6 +254,16 @@ public function direction(DropdownDirection $value): self return $new; } + /** + * Returns the list of links to appear in the dropdown. + * + * @return DropdownItem[] The links to appear in the dropdown. + */ + public function getItems(): array + { + return $this->items; + } + /** * List of links to appear in the dropdown. If this property is empty, the widget will not render anything. * @@ -482,6 +492,11 @@ public function toggleVariant(DropdownToggleVariant $value): self return $new; } + /** + * Renders the dropdown component. + * + * @return string The rendering result. + */ public function render(): string { $attributes = $this->attributes; @@ -531,6 +546,12 @@ public function render(): string } /** + * Renders the dropdown items. + * + * @param string|null $toggleId The ID of the toggle button. + * + * @return string The rendering result. + * * @psalm-param non-empty-string|null $toggleId */ private function renderItems(string|null $toggleId): string @@ -538,7 +559,7 @@ private function renderItems(string|null $toggleId): string $items = []; foreach ($this->items as $item) { - $items[] = $item->getContent(); + $items[] = $item->getLiContent(); } $ulTag = Ul::tag() @@ -554,6 +575,12 @@ private function renderItems(string|null $toggleId): string } /** + * Renders the dropdown toggle button. + * + * @param string|null $toggleId The ID of the toggle button. + * + * @return string The rendering result. + * * @psalm-param non-empty-string|null $toggleId */ private function renderToggle(string|null $toggleId): string @@ -608,6 +635,14 @@ private function renderToggle(string|null $toggleId): string ->render(); } + /** + * Renders the dropdown toggle link. + * + * @param array $toggleAttributes The HTML attributes for the toggle link. + * @param string $toggleContent The content of the toggle link. + * + * @return string The rendering result. + */ private function renderToggleLink(array $toggleAttributes, string $toggleContent): string { $toggleAttributes['role'] = 'button'; @@ -622,6 +657,11 @@ private function renderToggleLink(array $toggleAttributes, string $toggleContent ->render(); } + /** + * Renders the dropdown split button. + * + * @return string The rendering result. + */ private function renderToggleSplit(): string { if ($this->toggleLink) { diff --git a/src/DropdownItem.php b/src/DropdownItem.php index e39cef5b..a5baeeba 100644 --- a/src/DropdownItem.php +++ b/src/DropdownItem.php @@ -19,7 +19,11 @@ * For example, * * ```php - * echo DropdownItem::link('Dropdown link', '#'); + * DropdownItem::link('Dropdown link', '#'); + * DropdownItem::divider(); + * DropdownItem::header('Dropdown header'); + * DropdownItem::text('Dropdown text'); + * DropdownItem::listContent('Dropdown list content'); * ``` */ final class DropdownItem @@ -30,8 +34,10 @@ final class DropdownItem private const DROPDOWN_ITEM_DISABLED_CLASS = 'disabled'; private const DROPDOWN_ITEM_HEADER_CLASS = 'dropdown-header'; private const DROPDOWN_ITEM_TEXT_CLASS = 'dropdown-item-text'; + private string $content = ''; /** @psalm-suppress PropertyNotSetInConstructor */ - private Li $content; + private Li $liContent; + private string|null $url = null; private function __construct() { @@ -53,7 +59,7 @@ public static function divider(array $attributes = [], array $dividerAttributes unset($dividerAttributes['class']); - $dropdownItem->content = Li::tag() + $dropdownItem->liContent = Li::tag() ->addAttributes($attributes) ->addContent( "\n", @@ -90,7 +96,7 @@ public static function header( unset($headerAttributes['class']); - $dropdownItem->content = Li::tag() + $dropdownItem->liContent = Li::tag() ->addAttributes($attributes) ->addContent( "\n", @@ -152,7 +158,9 @@ public static function link( default => A::tag()->addAttributes($linkAttributes)->addClass($classesLink)->content($content)->url($url), }; - $dropdownItem->content = Li::tag()->addAttributes($attributes)->addContent("\n", $liContent, "\n"); + $dropdownItem->content = $content; + $dropdownItem->liContent = Li::tag()->addAttributes($attributes)->addContent("\n", $liContent, "\n"); + $dropdownItem->url = $url; return $dropdownItem; } @@ -169,7 +177,7 @@ public static function listContent(string|Stringable $content = '', array $attri { $dropdownItem = new self(); - $dropdownItem->content = Li::tag()->addAttributes($attributes)->addContent($content)->encode(false); + $dropdownItem->liContent = Li::tag()->addAttributes($attributes)->addContent($content)->encode(false); return $dropdownItem; } @@ -194,7 +202,7 @@ public static function text( unset($textAttributes['class']); - $dropdownItem->content = Li::tag() + $dropdownItem->liContent = Li::tag() ->addAttributes($attributes) ->addContent( "\n", @@ -209,10 +217,26 @@ public static function text( } /** - * @return Li Returns the encoded label content. + * @return string Returns the dropdown item content. */ - public function getContent(): Li + public function getContent(): string { return $this->content; } + + /** + * @return Li Returns the dropdown item
  • content. + */ + public function getLiContent(): Li + { + return $this->liContent; + } + + /** + * @return string|null Returns the URL of the dropdown item. + */ + public function getUrl(): string|null + { + return $this->url; + } } diff --git a/src/Nav.php b/src/Nav.php index a1f77c19..59d5dfda 100644 --- a/src/Nav.php +++ b/src/Nav.php @@ -5,14 +5,12 @@ namespace Yiisoft\Yii\Bootstrap5; use BackedEnum; -use InvalidArgumentException; use Stringable; use Yiisoft\Html\Html; use Yiisoft\Html\Tag\A; use Yiisoft\Html\Tag\Li; use Yiisoft\Html\Tag\Ul; -use function array_merge; use function implode; /** @@ -42,14 +40,35 @@ final class Nav extends \Yiisoft\Widget\Widget { private const NAV_CLASS = 'nav'; + private const NAV_ITEM_CLASS = 'nav-item'; private const NAV_ITEM_DROPDOWN_CLASS = 'nav-item dropdown'; + private const NAV_LINK_ACTIVE_CLASS = 'active'; + private const NAV_LINK_CLASS = 'nav-link'; + private const NAV_LINK_DISABLED_CLASS = 'disabled'; + private bool $activateItems = true; private array $attributes = []; private array $cssClasses = []; + private string $currentPath = ''; /** @psalm-var Dropdown[]|NavLink[] */ private array $items = []; private array $styleClasses = []; private string $tag = ''; + /** + * Whether to activate items by matching the currentPath with the `url` option in the nav items. + * + * @param bool $value Whether to activate items. Defaults to `true`. + * + * @return self A new instance with the specified activate items value. + */ + public function activateItems(bool $value): self + { + $new = clone $this; + $new->activateItems = $value; + + return $new; + } + /** * Adds a set of attributes for the nav component. * @@ -60,7 +79,7 @@ final class Nav extends \Yiisoft\Widget\Widget public function addAttributes(array $values): self { $new = clone $this; - $new->attributes = array_merge($this->attributes, $values); + $new->attributes = [...$this->attributes, ...$values]; return $new; } @@ -85,7 +104,7 @@ public function addAttributes(array $values): self public function addClass(BackedEnum|string|null ...$value): self { $new = clone $this; - $new->cssClasses = array_merge($new->cssClasses, $value); + $new->cssClasses = [...$this->cssClasses, ...$value]; return $new; } @@ -110,6 +129,23 @@ public function addCssStyle(array|string $value, bool $overwrite = true): self return $new; } + /** + * Sets the HTML attributes for the nav component. + * + * @param array $values Attribute values indexed by attribute names. + * + * @return self A new instance with the specified attributes. + * + * @see {\Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. + */ + public function attributes(array $values): self + { + $new = clone $this; + $new->attributes = $values; + + return $new; + } + /** * Replaces all existing CSS classes of the nav component with the provided ones. * @@ -133,6 +169,21 @@ public function class(BackedEnum|string|null ...$value): self return $new; } + /** + * The currentPath to be used to check the active state of the nav items. + * + * @param string $value The currentPath to be used to check the active state of the nav items. + * + * @return self A new instance with the specified currentPath. + */ + public function currentPath(string $value): self + { + $new = clone $this; + $new->currentPath = $value; + + return $new; + } + /** * List of links to appear in the nav. If this property is empty, the widget will not render anything. * @@ -168,8 +219,7 @@ public function items(Dropdown|NavLink ...$value): self public function styles(NavStyle|null ...$value): self { $new = clone $this; - - $new->styleClasses = array_merge($new->styleClasses, $value); + $new->styleClasses = [...$this->styleClasses, ...$value]; return $new; } @@ -230,16 +280,69 @@ private function renderItems(): array $items = []; foreach ($this->items as $item) { - if ($item instanceof NavLink && $item->getContent() instanceof A) { - throw new InvalidArgumentException('The nav item cannot be a link.'); - } - - $items[] = $item instanceof Dropdown ? $this->renderItemsDropdown($item) : $item->getContent(); + $items[] = match ($item instanceof Dropdown) { + true => $this->renderItemsDropdown($item), + default => $this->renderNavLink($item), + }; } return $items; } + /** + * Renders a dropdown item for the nav component. + * + * @param Dropdown $item The dropdown item to render. + * + * @return Li The rendered dropdown item. + */ + private function renderItemsDropdown(Dropdown $items): Li + { + $dropDownItems = $this->isItemActiveDropdown($items); + + return Li::tag() + ->addClass(self::NAV_ITEM_DROPDOWN_CLASS) + ->addContent( + "\n", + $dropDownItems + ->container(false) + ->toggleAsLink() + ->toggleClass('nav-link', 'dropdown-toggle') + ->toggleContent('Dropdown') + ->render(), + "\n" + ) + ->encode(false); + } + + /** + * Renders a link for the nav component. + * + * @param NavLink $item The link to render. + * + * @return A The rendered link. + */ + private function renderLink(NavLink $item): A + { + $attributes = $item->getUrlAttributes(); + + Html::addCssClass($attributes, [self::NAV_LINK_CLASS]); + + if ($this->isItemActive($item)) { + Html::addCssClass($attributes, [self::NAV_LINK_ACTIVE_CLASS]); + + $attributes['aria-current'] = 'page'; + } + + if ($item->isDisabled()) { + Html::addCssClass($attributes, [self::NAV_LINK_DISABLED_CLASS]); + + $attributes['aria-disabled'] = 'true'; + } + + return A::tag()->addAttributes($attributes)->addContent($item->getLabel())->href($item->getUrl()); + } + /** * Renders the links for the nav component. * @@ -250,37 +353,76 @@ private function renderLinks(): array $links = []; foreach ($this->items as $item) { - if ($item instanceof Dropdown || $item->getContent() instanceof Li) { - throw new InvalidArgumentException('The nav item cannot be a dropdown or a list item.'); + if ($item instanceof NavLink) { + $links[] = $this->renderLink($item); } - - $links[] = $item->getContent(); } return $links; } /** - * Renders a dropdown item for the nav component. + * Renders a nav link for the nav component. * - * @param Dropdown $item The dropdown item to render. + * @param NavLink $item The nav link to render. * - * @return Li The rendered dropdown item. + * @return Li The rendered nav link. */ - private function renderItemsDropdown(Dropdown $item): Li + private function renderNavLink(NavLink $item): Li { return Li::tag() - ->addClass(self::NAV_ITEM_DROPDOWN_CLASS) + ->addAttributes($item->getAttributes()) + ->addClass(self::NAV_ITEM_CLASS) ->addContent( "\n", - $item - ->container(false) - ->toggleAsLink() - ->toggleClass('nav-link', 'dropdown-toggle') - ->toggleContent('Dropdown') - ->render(), + $this->renderLink($item), "\n" - ) - ->encode(false); + ); + } + + /** + * Checks whether a menu item is active. + * + * This is done by checking if {@see currentPath} match that specified in the `url` option of the menu item. When + * the `url` option of a menu item is specified in terms of an array, its first element is treated as the + * currentPath for the item and the rest of the elements are the associated parameters. Only when its currentPath + * and parameters match {@see currentPath}, respectively, will a menu item be considered active. + * + * @param NavLink $item the menu item to be checked + * + * @return bool whether the menu item is active + */ + private function isItemActive(NavLink $item): bool + { + if ($item->isActive()) { + return true; + } + + return $item->getUrl() === $this->currentPath && $this->activateItems; + } + + /** + * Checks whether a dropdown item is active. + * + * This is done by checking if {@see currentPath} match that specified in the `url` option of the dropdown item. + * When the `url` option of a dropdown item is specified in terms of an array, its first element is treated as the + * currentPath for the item and the rest of the elements are the associated parameters. Only when its currentPath + * and parameters match {@see currentPath}, respectively, will a dropdown item be considered active. + * + * @param Dropdown $dropdown the dropdown item to be checked + * + * @return Dropdown the dropdown item with the active state set + */ + private function isItemActiveDropdown(Dropdown $dropdown): Dropdown + { + $items = $dropdown->getItems(); + + foreach ($items as $key => $value) { + if ($value->getUrl() === $this->currentPath && $this->activateItems) { + $items[$key] = DropdownItem::link($value->getContent(), $value->getUrl(), active: true); + } + } + + return $dropdown->items(...$items); } } diff --git a/src/NavLink.php b/src/NavLink.php index 20c0ebfc..19988b39 100644 --- a/src/NavLink.php +++ b/src/NavLink.php @@ -6,95 +6,68 @@ use InvalidArgumentException; use Stringable; -use Yiisoft\Html\Html; -use Yiisoft\Html\Tag\A; -use Yiisoft\Html\Tag\Li; /** * Represents a Bootstrap Nav item and link. + * + * ```php + * NavLink::to('Home', '/', true)->attributes(['class' => 'nav-link']); + * NavLink::to('Home', '/', disabled: true)->urlAttributes(['class' => 'nav-link']); */ final class NavLink { - private const NAV_LINK_ACTIVE_CLASS = 'active'; - private const NAV_LINK_DISABLED_CLASS = 'disabled'; - private const NAV_LINK_CLASS = 'nav-link'; - private const NAV_ITEM_CLASS = 'nav-item'; - /** @psalm-suppress PropertyNotSetInConstructor */ - private A|Li $content; + private bool $active = false; + private array $attributes = []; + private bool $disabled = false; + private string $label = ''; + private string|null $url = ''; + private array $urlAttributes = []; private function __construct() { } /** - * Creates a nav item with a link. + * Sets the HTML attributes for the nav item. * - * @param string|Stringable $label The label of the link. - * @param string|null $url The URL of the link. - * @param bool $active Whether the link is active. - * @param bool $disabled Whether the link is disabled. - * @param array $attributes Additional HTML attributes for the list item. - * @param array $linkAttributes Additional HTML attributes for the link. - * - * @throws InvalidArgumentException If the link is both active and disabled. + * @param array $values Attribute values indexed by attribute names. * * @return self A new instance with the specified attributes. + * + * @see {\Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. */ - public static function item( - string|Stringable $label = '', - string|null $url = null, - bool $active = false, - bool $disabled = false, - array $attributes = [], - array $linkAttributes = [], - ): self { - $navlink = new self(); - - $classes = $attributes['class'] ?? null; - $linkClasses = $linkAttributes['class'] ?? null; - - unset($attributes['class'], $linkAttributes['class']); - - if ($active === true && $disabled === true) { - throw new InvalidArgumentException('The nav item cannot be active and disabled at the same time.'); - } - - Html::addCssClass($linkAttributes, [self::NAV_LINK_CLASS]); - - if ($active === true) { - Html::addCssClass($linkAttributes, [self::NAV_LINK_ACTIVE_CLASS]); - - $linkAttributes['aria-current'] = 'page'; - } - - if ($disabled === true) { - Html::addCssClass($linkAttributes, [self::NAV_LINK_DISABLED_CLASS]); + public function attributes(array $values): self + { + $new = clone $this; + $new->attributes = $values; - $linkAttributes['aria-disabled'] = 'true'; - } + return $new; + } - $navlink->content = Li::tag() - ->addClass( - self::NAV_ITEM_CLASS, - $classes, - ) - ->addContent( - "\n", - A::tag()->addAttributes($linkAttributes)->addClass($linkClasses)->addContent($label)->href($url), - "\n", - ); + /** + * Sets the HTML attributes for the nav item link. + * + * @param array $values Attribute values indexed by attribute names. + * + * @return self A new instance with the specified attributes. + * + * @see {\Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. + */ + public function urlAttributes(array $values): self + { + $new = clone $this; + $new->urlAttributes = $values; - return $navlink; + return $new; } /** - * Creates a nav link. + * Creates a nav item. * * @param string|Stringable $label The label of the link. * @param string|null $url The URL of the link. * @param bool $active Whether the link is active. * @param bool $disabled Whether the link is disabled. - * @param array $attributes Additional HTML attributes for the link. * * @throws InvalidArgumentException If the link is both active and disabled. * @@ -105,42 +78,78 @@ public static function to( string|null $url = null, bool $active = false, bool $disabled = false, - array $attributes = [], ): self { $navlink = new self(); - $classes = $attributes['class'] ?? null; - - unset($attributes['class']); - if ($active === true && $disabled === true) { - throw new InvalidArgumentException('The nav link cannot be active and disabled at the same time.'); + throw new InvalidArgumentException('A nav link cannot be both active and disabled.'); } - Html::addCssClass($attributes, [self::NAV_LINK_CLASS]); + $navlink->active = $active; + $navlink->disabled = $disabled; + $navlink->label = $label; + $navlink->url = $url; - if ($active === true) { - Html::addCssClass($attributes, [self::NAV_LINK_ACTIVE_CLASS]); + return $navlink; + } - $attributes['aria-current'] = 'page'; - } + /** + * Returns the HTML attributes for the nav item. + * + * @return array The HTML attributes for the nav item. + */ + public function getAttributes(): array + { + return $this->attributes; + } - if ($disabled === true) { - Html::addCssClass($attributes, [self::NAV_LINK_DISABLED_CLASS]); + /** + * Returns the label of the nav item. + * + * @return string The label of the nav item. + */ + public function getLabel(): string + { + return $this->label; + } - $attributes['aria-disabled'] = 'true'; - } + /** + * Returns the URL of the nav item. + * + * @return string|null The URL of the nav item. + */ + public function getUrl(): string|null + { + return $this->url; + } - $navlink->content = A::tag()->addAttributes($attributes)->addClass($classes)->addContent($label)->href($url); + /** + * Returns the HTML attributes for the nav item link. + * + * @return array The HTML attributes for the nav item link. + */ + public function getUrlAttributes(): array + { + return $this->urlAttributes; + } - return $navlink; + /** + * Returns whether the nav item is active. + * + * @return bool Whether the nav item is active. + */ + public function isActive(): bool + { + return $this->active; } /** - * @return A|Li Returns the encoded label content. + * Returns whether the nav item is disabled. + * + * @return bool Whether the nav item is disabled. */ - public function getContent(): A|Li + public function isDisabled(): bool { - return $this->content; + return $this->disabled; } } diff --git a/tests/DropdownItemTest.php b/tests/DropdownItemTest.php index cc68ddd0..14603722 100644 --- a/tests/DropdownItemTest.php +++ b/tests/DropdownItemTest.php @@ -25,7 +25,7 @@ public function testDivider(): void
  • HTML, - $divider->getContent()->render(), + $divider->getLiContent()->render(), ); } @@ -39,7 +39,7 @@ public function testDividerWithAttributes(): void HTML, - $divider->getContent()->render(), + $divider->getLiContent()->render(), ); } @@ -53,7 +53,7 @@ public function testDividerWithDividerAttributes(): void HTML, - $divider->getContent()->render(), + $divider->getLiContent()->render(), ); } @@ -67,7 +67,7 @@ public function testHeader(): void HTML, - $header->getContent()->render(), + $header->getLiContent()->render(), ); } @@ -81,7 +81,7 @@ public function testHeaderWithAttributes(): void HTML, - $header->getContent()->render(), + $header->getLiContent()->render(), ); } @@ -95,7 +95,7 @@ public function testHeaderWithHeaderTag(): void HTML, - $header->getContent()->render(), + $header->getLiContent()->render(), ); } @@ -117,7 +117,7 @@ public function testHeaderWithHeaderAttributes(): void HTML, - $header->getContent()->render(), + $header->getLiContent()->render(), ); } @@ -131,7 +131,7 @@ public function testLink(): void label HTML, - $link->getContent()->render(), + $link->getLiContent()->render(), ); } @@ -145,7 +145,7 @@ public function testLinkWithActive(): void label HTML, - $link->getContent()->render(), + $link->getLiContent()->render(), ); } @@ -159,7 +159,7 @@ public function testLinkWithDisabled(): void label HTML, - $link->getContent()->render(), + $link->getLiContent()->render(), ); } @@ -173,7 +173,7 @@ public function testLinkWithAttributes(): void label HTML, - $link->getContent()->render(), + $link->getLiContent()->render(), ); } @@ -187,7 +187,7 @@ public function testLinkWithLinkAttributes(): void label HTML, - $link->getContent()->render(), + $link->getLiContent()->render(), ); } @@ -201,7 +201,7 @@ public function testLinkWithLinkAttributesAndActive(): void label HTML, - $link->getContent()->render(), + $link->getLiContent()->render(), ); } @@ -215,7 +215,7 @@ public function testLinkWithLinkAttributesAndDisabled(): void label HTML, - $link->getContent()->render(), + $link->getLiContent()->render(), ); } @@ -237,7 +237,7 @@ public function testText(): void content HTML, - $text->getContent()->render(), + $text->getLiContent()->render(), ); } @@ -251,7 +251,7 @@ public function testTextWithAttributes(): void content HTML, - $text->getContent()->render(), + $text->getLiContent()->render(), ); } @@ -265,7 +265,7 @@ public function testTextWithTextAttributes(): void content HTML, - $text->getContent()->render(), + $text->getLiContent()->render(), ); } } diff --git a/tests/NavLinkTest.php b/tests/NavLinkTest.php index c1b4567f..2e4fbe6e 100644 --- a/tests/NavLinkTest.php +++ b/tests/NavLinkTest.php @@ -4,8 +4,8 @@ namespace Yiisoft\Yii\Bootstrap5\Tests; +use InvalidArgumentException; use Yiisoft\Yii\Bootstrap5\NavLink; -use Yiisoft\Yii\Bootstrap5\Tests\Support\Assert; /** * Tests for `DropdownItem`. @@ -14,111 +14,19 @@ */ final class NavLinkTest extends \PHPUnit\Framework\TestCase { - public function testItem(): void + public function testImmutability(): void { - Assert::equalsWithoutLE( - << - Link - - HTML, - NavLink::item('Link', '#')->getContent()->render(), - ); - } - - public function testItemWithActive(): void - { - Assert::equalsWithoutLE( - << - Active - - HTML, - NavLink::item('Active', '#', active: true)->getContent()->render(), - ); - } - - public function testItemWithActiveAndDisabled(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The nav item cannot be active and disabled at the same time.'); - - NavLink::item('Active and Disabled', '#', active: true, disabled: true); - } + $navLink = NavLink::to('Home', '/', true); - public function testItemWithAttributes(): void - { - Assert::equalsWithoutLE( - << - Link - - HTML, - NavLink::item('Link', '#', attributes: ['class' => 'test-class'])->getContent()->render(), - ); + $this->assertNotSame($navLink, $navLink->attributes([])); + $this->assertNotSame($navLink, $navLink->urlAttributes([])); } - public function testItemWithDisabled(): void + public function testThrowExceptionwithActiveAndDisableTrueValue(): void { - Assert::equalsWithoutLE( - << - Disabled - - HTML, - NavLink::item('Disabled', '#', disabled: true)->getContent()->render(), - ); - } + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('A nav link cannot be both active and disabled.'); - public function testItemWitkLinkAttributes(): void - { - Assert::equalsWithoutLE( - << - Link - - HTML, - NavLink::item('Link', '#', linkAttributes: ['target' => '_blank'])->getContent()->render(), - ); - } - - public function testTo(): void - { - $this->assertSame( - 'Link', - NavLink::to('Link', '/test')->getContent()->render(), - ); - } - - public function testToWithActive(): void - { - $this->assertSame( - 'Active', - NavLink::to('Active', '/test', active: true)->getContent()->render(), - ); - } - - public function testToWithActiveAndDisabled(): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('The nav link cannot be active and disabled at the same time.'); - - NavLink::to('Active and Disabled', '/test', active: true, disabled: true); - } - - public function testToWithAttributes(): void - { - $this->assertSame( - 'Link', - NavLink::to('Link', '/test', attributes: ['class' => 'test-class'])->getContent()->render(), - ); - } - - public function testToWithDisabled(): void - { - $this->assertSame( - 'Disabled', - NavLink::to('Disabled', '/test', disabled: true)->getContent()->render(), - ); + NavLink::to('Home', '/', true, true); } } diff --git a/tests/NavTest.php b/tests/NavTest.php index 1ff2fd95..260d7b3e 100644 --- a/tests/NavTest.php +++ b/tests/NavTest.php @@ -4,7 +4,6 @@ namespace Yiisoft\Yii\Bootstrap5\Tests; -use InvalidArgumentException; use Yiisoft\Yii\Bootstrap5\Dropdown; use Yiisoft\Yii\Bootstrap5\DropdownItem; use Yiisoft\Yii\Bootstrap5\Nav; @@ -42,10 +41,10 @@ public function testAddAttributes(): void Nav::widget() ->addAttributes(['data-test' => 'test']) ->items( - NavLink::item('Active', '#', active: true), - NavLink::item('Link', url: '#'), - NavLink::item('Link', url: '#'), - NavLink::item('Disabled', '#', disabled: true), + NavLink::to('Active', '#', active: true), + NavLink::to('Link', url: '#'), + NavLink::to('Link', url: '#'), + NavLink::to('Disabled', '#', disabled: true), ) ->render(), ); @@ -56,10 +55,10 @@ public function testAddClass(): void $navWidget = Nav::widget() ->addClass('test-class', null, BackgroundColor::PRIMARY) ->items( - NavLink::item('Active', '#', active: true), - NavLink::item('Link', url: '#'), - NavLink::item('Link', url: '#'), - NavLink::item('Disabled', '#', disabled: true), + NavLink::to('Active', '#', active: true), + NavLink::to('Link', url: '#'), + NavLink::to('Link', url: '#'), + NavLink::to('Disabled', '#', disabled: true), ); Assert::equalsWithoutLE( @@ -108,10 +107,10 @@ public function testAddCssStyle(): void $navWidget = Nav::widget() ->addCssStyle(['color' => 'red']) ->items( - NavLink::item('Active', '#', active: true), - NavLink::item('Link', url: '#'), - NavLink::item('Link', url: '#'), - NavLink::item('Disabled', '#', disabled: true), + NavLink::to('Active', '#', active: true), + NavLink::to('Link', url: '#'), + NavLink::to('Link', url: '#'), + NavLink::to('Disabled', '#', disabled: true), ); Assert::equalsWithoutLE( @@ -160,10 +159,10 @@ public function testAddCssStyleWithOverwriteFalse(): void $navWidget = Nav::widget() ->addCssStyle(['color' => 'red']) ->items( - NavLink::item('Active', '#', active: true), - NavLink::item('Link', url: '#'), - NavLink::item('Link', url: '#'), - NavLink::item('Disabled', '#', disabled: true), + NavLink::to('Active', '#', active: true), + NavLink::to('Link', url: '#'), + NavLink::to('Link', url: '#'), + NavLink::to('Disabled', '#', disabled: true), ); Assert::equalsWithoutLE( @@ -207,6 +206,23 @@ public function testAddCssStyleWithOverwriteFalse(): void ); } + public function testAttributes(): void + { + Assert::equalsWithoutLE( + << + + + HTML, + Nav::widget() + ->attributes(['data-test' => 'test']) + ->items(NavLink::to('Active', '#', active: true)) + ->render(), + ); + } + /** * @link https://getbootstrap.com/docs/5.3/components/navs-tabs/#base-nav */ @@ -231,23 +247,15 @@ public function testBaseNav(): void HTML, Nav::widget() ->items( - NavLink::item('Active', '#', active: true), - NavLink::item('Link', url: '#'), - NavLink::item('Link', url: '#'), - NavLink::item('Disabled', '#', disabled: true), + NavLink::to('Active', '#', active: true), + NavLink::to('Link', url: '#'), + NavLink::to('Link', url: '#'), + NavLink::to('Disabled', '#', disabled: true), ) ->render(), ); } - public function testBaseNavException(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The nav item cannot be a link.'); - - Nav::widget()->items(NavLink::to('Active', '#', active: true))->render(); - } - /** * @link https://getbootstrap.com/docs/5.3/components/navs-tabs/#base-nav */ @@ -274,14 +282,6 @@ public function testBaseNavWithTag(): void ); } - public function testBaseNavWithTagException(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The nav item cannot be a dropdown or a list item.'); - - Nav::widget()->items(NavLink::item('Active', '#', active: true))->tag('nav')->render(); - } - public function testClass(): void { Assert::equalsWithoutLE( @@ -305,10 +305,238 @@ public function testClass(): void ->addClass('test-class') ->class('custom-class', 'another-class', BackgroundColor::PRIMARY) ->items( - NavLink::item('Active', '#', active: true), - NavLink::item('Link', url: '#'), - NavLink::item('Link', url: '#'), - NavLink::item('Disabled', '#', disabled: true), + NavLink::to('Active', '#', active: true), + NavLink::to('Link', url: '#'), + NavLink::to('Link', url: '#'), + NavLink::to('Disabled', '#', disabled: true), + ) + ->render(), + ); + } + + public function testCurrentPath(): void + { + Assert::equalsWithoutLE( + << + + + + + + HTML, + Nav::widget() + ->currentPath('/test/link') + ->items( + NavLink::to('Active', '/test'), + NavLink::to('Link', '/test/link'), + NavLink::to('Link', '/test/link/another-link'), + NavLink::to('Disabled', '/test/disabled', disabled: true), + ) + ->render(), + ); + } + + public function testCurrentPathAndActivateItemsWithFalseValue(): void + { + Assert::equalsWithoutLE( + << + + + + + + HTML, + Nav::widget() + ->activateItems(false) + ->currentPath('/test/link') + ->items( + NavLink::to('Active', '/test'), + NavLink::to('Link', '/test/link'), + NavLink::to('Link', '/test/link/another-link'), + NavLink::to('Disabled', '/test/disabled', disabled: true), + ) + ->render(), + ); + } + + public function testDropdownAndCurrentPath(): void + { + Assert::equalsWithoutLE( + << + + + + + + HTML, + Nav::widget() + ->currentPath('/test/link/another-action') + ->items( + NavLink::to('Active', '/test'), + Dropdown::widget() + ->items( + DropdownItem::link('Action', '/test/link/action'), + DropdownItem::link('Another action', '/test/link/another-action'), + DropdownItem::link('Something else here', '/test/link/something-else'), + DropdownItem::divider(), + DropdownItem::link('Separated link', '/test/link/separated-link'), + ), + NavLink::to('Link', '/test/link'), + NavLink::to('Disabled', '/test/disabled', disabled: true), + ) + ->render(), + ); + } + + public function testDropdownAndCurrentPathAndActivateItemsWithFalseValue(): void + { + Assert::equalsWithoutLE( + << + + + + + + HTML, + Nav::widget() + ->activateItems(false) + ->currentPath('/test/link/another-action') + ->items( + NavLink::to('Active', '/test'), + Dropdown::widget() + ->items( + DropdownItem::link('Action', '/test/link/action'), + DropdownItem::link('Another action', '/test/link/another-action'), + DropdownItem::link('Something else here', '/test/link/something-else'), + DropdownItem::divider(), + DropdownItem::link('Separated link', '/test/link/separated-link'), + ), + NavLink::to('Link', '/test/link'), + NavLink::to('Disabled', '/test/disabled', disabled: true), + ) + ->render(), + ); + } + + public function testDropdownExplicitActive(): void + { + Assert::equalsWithoutLE( + << + + + + + + HTML, + Nav::widget() + ->items( + NavLink::to('Active', '/test'), + Dropdown::widget() + ->items( + DropdownItem::link('Action', '/test/link/action'), + DropdownItem::link('Another action', '/test/link/another-action', active: true), + DropdownItem::link('Something else here', '/test/link/something-else'), + DropdownItem::divider(), + DropdownItem::link('Separated link', '/test/link/separated-link'), + ), + NavLink::to('Link', '/test/link'), + NavLink::to('Disabled', '/test/disabled', disabled: true), ) ->render(), ); @@ -338,10 +566,10 @@ public function testHorizontalAlignment(): void HTML, Nav::widget() ->items( - NavLink::item('Active', '#', active: true), - NavLink::item('Link', url: '#'), - NavLink::item('Link', url: '#'), - NavLink::item('Disabled', '#', disabled: true), + NavLink::to('Active', '#', active: true), + NavLink::to('Link', url: '#'), + NavLink::to('Link', url: '#'), + NavLink::to('Disabled', '#', disabled: true), ) ->styles(NavStyle::HORIZONTAL_ALIGNMENT) ->render(), @@ -352,14 +580,49 @@ public function testImmutability(): void { $navWidget = Nav::widget(); + $this->assertNotSame($navWidget, $navWidget->activateItems(false)); $this->assertNotSame($navWidget, $navWidget->addAttributes([])); $this->assertNotSame($navWidget, $navWidget->addClass('')); $this->assertNotSame($navWidget, $navWidget->addCssStyle('')); + $this->assertNotSame($navWidget, $navWidget->attributes([])); $this->assertNotSame($navWidget, $navWidget->class('')); - $this->assertNotSame($navWidget, $navWidget->items(NavLink::item(''))); + $this->assertNotSame($navWidget, $navWidget->currentPath('')); + $this->assertNotSame($navWidget, $navWidget->items(NavLink::to(''))); $this->assertNotSame($navWidget, $navWidget->styles(NavStyle::FILL)); } + public function testNavLinkWithAttributes(): void + { + Assert::equalsWithoutLE( + << + + + HTML, + Nav::widget() + ->items(NavLink::to('Active', '#', active: true)->attributes(['data-test' => 'test'])) + ->render(), + ); + } + + public function testNavLinkWithUrlAttributes(): void + { + Assert::equalsWithoutLE( + << + + + HTML, + Nav::widget() + ->items(NavLink::to('Active', '#', active: true)->urlAttributes(['data-test' => 'test'])) + ->render(), + ); + } + /** * @link https://getbootstrap.com/docs/5.3/components/navs-tabs/#pills */ @@ -384,10 +647,10 @@ public function testPills(): void HTML, Nav::widget() ->items( - NavLink::item('Active', '#', active: true), - NavLink::item('Link', url: '#'), - NavLink::item('Link', url: '#'), - NavLink::item('Disabled', '#', disabled: true), + NavLink::to('Active', '#', active: true), + NavLink::to('Link', url: '#'), + NavLink::to('Link', url: '#'), + NavLink::to('Disabled', '#', disabled: true), ) ->styles(NavStyle::PILLS) ->render(), @@ -435,7 +698,7 @@ public function testPillsWithDropdown(): void HTML, Nav::widget() ->items( - NavLink::item('Active', '#', active: true), + NavLink::to('Active', '#', active: true), Dropdown::widget() ->items( DropdownItem::link('Action', '#'), @@ -444,8 +707,8 @@ public function testPillsWithDropdown(): void DropdownItem::divider(), DropdownItem::link('Separated link', '#'), ), - NavLink::item('Link', url: '#'), - NavLink::item('Disabled', '#', disabled: true), + NavLink::to('Link', url: '#'), + NavLink::to('Disabled', '#', disabled: true), ) ->styles(NavStyle::PILLS) ->render(), @@ -476,10 +739,10 @@ public function testPillsWithFill(): void HTML, Nav::widget() ->items( - NavLink::item('Active', '#', active: true), - Navlink::item('Much longer nav link', url: '#'), - NavLink::item('Link', url: '#'), - NavLink::item('Disabled', '#', disabled: true), + NavLink::to('Active', '#', active: true), + Navlink::to('Much longer nav link', url: '#'), + NavLink::to('Link', url: '#'), + NavLink::to('Disabled', '#', disabled: true), ) ->styles(NavStyle::PILLS, NavStyle::FILL) ->render(), @@ -510,10 +773,10 @@ public function testPillsWithJustify(): void HTML, Nav::widget() ->items( - NavLink::item('Active', '#', active: true), - Navlink::item('Much longer nav link', url: '#'), - NavLink::item('Link', url: '#'), - NavLink::item('Disabled', '#', disabled: true), + NavLink::to('Active', '#', active: true), + Navlink::to('Much longer nav link', url: '#'), + NavLink::to('Link', url: '#'), + NavLink::to('Disabled', '#', disabled: true), ) ->styles(NavStyle::PILLS, NavStyle::JUSTIFY) ->render(), @@ -527,7 +790,7 @@ public function testNavBar(): void { Assert::equalsWithoutLE( << +