Files
livewire-material/tests/Feature/Components/NavigationRailTest.php
T
Andreas Reinhold / reiniandClaude Opus 5 4a4982b1ef Reach M3's 48px target from every 40px navigation control
The theme toggle and the account-menu avatar are drawn 40px and had no target
extension; both now carry `touch-target`, and the avatar's clipping moves from
the button (which would have cut the pseudo-target off) to the <img>. The rail's
hand-rolled `after:` target becomes the same utility.
Plan: docs/plans/material-3-alignment.md, step 14 (navigation N-01).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 05:52:09 +02:00

80 lines
4.0 KiB
PHP

<?php
it('draws each mode, falling back to collapsible', function (string $mode, string $expected) {
expect((string) $this->blade('<x-navigation-rail :mode="$mode"><x-navigation-rail-item label="Inbox" icon="inbox" link="/inbox" /></x-navigation-rail>', ['mode' => $mode]))
->toContain("data-navigation-rail=\"{$expected}\"")
->toContain('<nav')
->toContain('aria-label="Main"');
})->with([
['collapsed', 'collapsed'],
['expanded', 'expanded'],
['collapsible', 'collapsible'],
['modal', 'modal'],
['adaptive', 'adaptive'],
['sideways', 'collapsible'],
]);
it('gives the collapsible, modal and adaptive rails a menu button and the store', function () {
$collapsible = (string) $this->blade('<x-navigation-rail />');
expect($collapsible)
->toContain('x-data="materialNavigationRail(\'collapsible\')"')
->toContain('data-navigation-rail-menu')
->toContain('aria-label="Collapse navigation"')
->toContain('aria-expanded="true"')
// The 40px menu button reaches M3's 48px target through the shared utility (N-01).
->toContain('touch-target')
->not->toContain('data-navigation-rail-scrim')
->not->toContain('x-trap')
->and((string) $this->blade('<x-navigation-rail mode="modal" />'))
->toContain('aria-label="Expand navigation"')
->toContain('data-navigation-rail-scrim')
->toContain('x-trap.inert.noscroll="open"')
->and((string) $this->blade('<x-navigation-rail mode="expanded" />'))
->not->toContain('data-navigation-rail-menu')
->not->toContain('x-data')
->and((string) $this->blade('<x-navigation-rail mode="collapsible" :menu="false" />'))->not->toContain('data-navigation-rail-menu');
});
it('keeps the header and footer out of the scrolling destinations', function () {
$html = (string) $this->blade(<<<'BLADE'
<x-navigation-rail mode="expanded" width="20rem">
<x-slot:brand><span>Brand</span></x-slot:brand>
<x-slot:header><button>FAB</button></x-slot:header>
<x-navigation-rail-item label="Inbox" icon="inbox" link="/inbox" />
<x-slot:footer><button>Account</button></x-slot:footer>
</x-navigation-rail>
BLADE);
expect($html)
->toContain('--navigation-rail-width: 20rem')
->toMatch('/data-navigation-rail-header.*Brand.*FAB.*data-navigation-rail-destinations.*Inbox.*data-navigation-rail-footer.*Account/s');
});
it('draws a destination in both shapes, with its count on the icon and at the end', function () {
$html = (string) $this->blade('<x-navigation-rail-item label="Inbox" icon="inbox" link="/inbox" active badge="12" />');
expect($html)
->toContain('<a data-navigation-rail-item')
->toContain('wire:navigate')
->toContain('aria-current="page"')
->toContain('data-navigation-indicator')
->toContain('<span data-navigation-label>Inbox</span>')
->toContain('hidden rail-collapsed:contents')
->toContain('rail-collapsed:hidden')
->toContain('<span class="sr-only">, 12</span>')
->and(substr_count($html, '>12</span>'))->toBe(2)
->and((string) $this->blade('<x-navigation-rail-item label="Inbox" icon="inbox" link="/inbox" no-wire-navigate />'))->not->toContain('wire:navigate')
->and((string) $this->blade('<x-navigation-rail-item label="Inbox" icon="inbox" />'))->toContain('<button data-navigation-rail-item');
});
it('groups destinations under a heading that names the group', function () {
$html = (string) $this->blade('<x-navigation-rail-section label="Labels"><x-navigation-rail-item label="Travel" icon="flight" link="/travel" /></x-navigation-rail-section>');
preg_match('/aria-labelledby="([^"]+)"/', $html, $labelledBy);
expect($html)->toContain('role="group"')
->and($labelledBy[1] ?? null)->not->toBeNull()
->and($html)->toContain("<p id=\"{$labelledBy[1]}\" data-navigation-rail-heading>Labels</p>");
});