blade('', ['mode' => $mode])) ->toContain("data-navigation-rail=\"{$expected}\"") ->toContain('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(''); 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('')) ->toContain('aria-label="Expand navigation"') ->toContain('data-navigation-rail-scrim') ->toContain('x-trap.inert.noscroll="open"') ->and((string) $this->blade('')) ->not->toContain('data-navigation-rail-menu') ->not->toContain('x-data') ->and((string) $this->blade(''))->not->toContain('data-navigation-rail-menu'); }); it('keeps the header and footer out of the scrolling destinations', function () { $html = (string) $this->blade(<<<'BLADE' Brand 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('takes M3\'s optional divider and turns the container fill off', function () { expect((string) $this->blade('')) ->toContain('data-divider') ->toContain('data-fill="false"') ->and((string) $this->blade('')) ->not->toContain('data-divider') ->not->toContain('data-fill'); expect(file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css')) // Neither reaches a rail open over a scrim, which is a surface over the page (N-22). ->toContain('[data-navigation-rail][data-divider]:not([data-open]) > [data-navigation-rail-panel] {') ->toContain("[data-navigation-rail][data-fill='false']:not([data-open]) > [data-navigation-rail-panel] {") // A collapsible rail is held to 96px where M3 asks for a navigation bar instead (N-24). ->toMatch("/\\[data-navigation-rail='collapsible'\\] \\{\\s+width: 6rem;/"); }); it('flattens a FAB nested in the rail header and morphs its label', function () { // Both rules hang off `data-fab` on 's root, and both are unlayered, because what they // beat — the FAB's shadow, its gap, an extended FAB's minimum width — are utilities. expect(file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css')) // A nested FAB rests at elevation 0, not the 3 a standalone one has (N-03). ->toContain('[data-navigation-rail-header] [data-fab],') ->toContain('[data-navigation-rail-header] [data-fab]:hover {') // One FAB whose label springs shut, not two swapped by display (N-23). ->toContain('[data-navigation-rail-header] [data-fab] > span {') ->toMatch('/@variant rail-collapsed \{\s+min-width: 0;\s+aspect-ratio: 1;\s+gap: 0;/'); }); it('draws a destination in both shapes, with its count on the icon and at the end', function () { $html = (string) $this->blade(''); expect($html) ->toContain('toContain('wire:navigate') ->toContain('aria-current="page"') ->toContain('data-navigation-indicator') ->toContain('Inbox') ->toContain('hidden rail-collapsed:contents') ->toContain('rail-collapsed:hidden') ->toContain(', 12') ->and(substr_count($html, '>12'))->toBe(2) ->and((string) $this->blade(''))->not->toContain('wire:navigate') ->and((string) $this->blade(''))->toContain('