Files
livewire-material/tests/Feature/Components/PaneTest.php
T
Andreas Reinhold / reiniandClaude Sonnet 5 b7641a35c2 Draw the app bar without Tailwind
Plan step 36 (navigation group, first stream): <x-app-bar>'s class
lists move into resources/css/components/app-bar.css, keyed on
data-md-app-bar (with data-md-variant/-sticky/-subtitled/-scrolled/
-collapsed) and its parts data-md-app-bar-row/-leading/-headline/
-title/-subtitle/-search/-trailing/-action/-overflow/-expanded. The
overflow menu (two icon buttons below 600px, four from it, N-09's
312px search cap and N-11's three-column centred row) already matched
the audit; only its hooks, units and layer needed to change. Every
length is px, spacing through the measurement tokens where one
matches (M3's own "4dp leading/trailing space" and "16dp" headline
inset had no exact token, so those stay literal). Imports button.css,
menu.css and menu-item.css for the overflow menu the view renders.

Hooks renamed: every unprefixed data-app-bar-* attribute and
data-scrolled/data-collapsed to data-md-*, updated in
tests/Feature/Components/{AppBarTest,PaneTest}.php and
tests/Browser/{BarsTest,NavigationTest}.php. app-bar.js needed no
change: it reads the bar through $root, not by hook name.

tests/Feature/Components/NavigationStylesheetsTest.php is the
navigation twin of ContainmentStylesheetsTest.php, on the same
tests/Support/ViewClasses.php rule; its dataset starts at 'app-bar'
and a 'tabs' entry (once that commit lands) will read both
tabs.blade.php and tab.blade.php, since they share one stylesheet.

Added the owed browser tests (docs/plans/material-3-browser-tests.md):
the overflow at 599/600px, keyboard in its menu, a menu closing once
its width no longer shows it, and a wire:click action from both the
icon button and the menu item form.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 23:21:54 +02:00

92 lines
4.8 KiB
PHP

<?php
it('draws a content region with a body and no app bar when it has no title', function () {
$html = (string) $this->blade('<x-pane>The page</x-pane>');
expect(layoutRoot($html))->toMatchArray(['<' => 'div', 'data-md-pane' => 'data-md-pane'])
->not->toHaveKeys(['data-md-width', 'data-md-hide-below', 'data-md-hide-from'])
->and($html)->toMatch('/<div data-md-pane-body>The page<\/div>/')
->not->toContain('data-md-app-bar')
->not->toContain('data-md-pane-navigation');
});
it('gives the pane its own app bar, a direct child spanning it, with the title, subtitle and actions', function () {
$html = (string) $this->blade(<<<'BLADE'
<x-pane title="Settings" subtitle="Your account" heading="h2">
<x-slot:actions><span>ACTIONS</span></x-slot:actions>
BODY
</x-pane>
BLADE);
expect($html)
->toMatch('/^\s*<div data-md-pane[^>]*>\s*<header\b[^>]*data-md-app-bar[^>]*data-md-pane-bar/s')
->toContain('<h2 data-md-app-bar-title>Settings</h2>')
->toContain('<p data-md-app-bar-subtitle>Your account</p>')
->toMatch('/<div data-md-app-bar-trailing><span>ACTIONS<\/span><\/div>/')
->toContain('data-md-sticky')
->toMatch('/<\/header>\s*<div data-md-pane-body>\s*BODY\s*<\/div>/s')
->and((string) $this->blade('<x-pane title="Settings" :sticky="false" />'))->not->toContain('data-md-sticky');
});
it('draws a bar for actions alone, and names the heading h1 by default', function () {
expect((string) $this->blade('<x-pane><x-slot:actions><span>ACTIONS</span></x-slot:actions></x-pane>'))->toContain('data-md-pane-bar')
->and((string) $this->blade('<x-pane title="Inbox" />'))->toContain('<h1 data-md-app-bar-title>Inbox</h1>');
});
it('puts the section navigation under the bar and above the body', function () {
$html = (string) $this->blade(<<<'BLADE'
<x-pane title="Settings">
<x-slot:navigation><nav>SECTIONS</nav></x-slot:navigation>
BODY
</x-pane>
BLADE);
expect($html)->toMatch('/<\/header>\s*<div data-md-pane-navigation><nav>SECTIONS<\/nav><\/div>\s*<div data-md-pane-body>\s*BODY/s')
// It is the pane's section navigation, not the bar's leading button.
->not->toContain('data-md-app-bar-leading');
});
it('leads the bar with a back link, a back action for a list-detail, or the leading slot', function () {
$link = (string) $this->blade('<x-pane title="Recipients" back="/shares" />');
$action = (string) $this->blade('<x-pane title="Message" back />');
$leading = (string) $this->blade('<x-pane title="Inbox" back><x-slot:leading><button>MENU</button></x-slot:leading></x-pane>');
expect($link)->toMatch('/data-md-app-bar-leading><span data-md-pane-back\s*>/')
->toMatch('/<a[^>]*href="\/shares"[^>]*aria-label="Back"/s')
->not->toContain('data-md-list-detail-back')
->and($action)->toMatch('/<span data-md-pane-back\s+data-md-list-detail-back\s*>/')
->toMatch('/<button[^>]*aria-label="Back"[^>]*x-on:click="back\(\)"/s')
->and($leading)->toContain('<div data-md-app-bar-leading><button>MENU</button></div>')
->not->toContain('data-md-pane-back');
});
it('caps its width with M3\'s narrow measure or the wider steps', function () {
foreach (['full', 'narrow', 'medium', 'wide'] as $width) {
expect(layoutRoot((string) $this->blade('<x-pane :width="$width" />', ['width' => $width]))['data-md-width'])->toBe($width);
}
expect(layoutRoot((string) $this->blade('<x-pane width="40rem" />')))->not->toHaveKey('data-md-width');
});
it('takes the element, the visibility props and the caller\'s class and style', function () {
expect(layoutRoot((string) $this->blade('<x-pane as="section" hide-below="expanded" class="checkout" style="max-width: 50rem" aria-labelledby="checkout-title" />')))
->toMatchArray([
'<' => 'section',
'data-md-hide-below' => 'expanded',
'class' => 'checkout',
'style' => 'max-width: 50rem;',
'aria-labelledby' => 'checkout-title',
]);
});
it('keeps M3\'s margin on the body, once, in the layout layer', function () {
$css = (string) file_get_contents(__DIR__.'/../../../resources/css/layout/pane.css');
expect($css)->toContain('@layer material.layout')
->toContain('padding-inline: var(--md-layout-margin, var(--md-sys-measurement-space200));')
->toContain("@media (width >= 600px) {\n padding-inline: var(--md-layout-margin, var(--md-sys-measurement-space300));")
->toContain("[data-md-pane-body] > * {\n --md-layout-margin: 0px;")
->toContain("[data-md-pane][data-md-width='narrow'] {\n max-inline-size: 40rem;")
->and((string) file_get_contents(__DIR__.'/../../../resources/css/layout.css'))->toContain("@import './layout/pane.css';");
});