Plan step 37: all.css replaces the two interim import lists with one entry for an application that wants everything — the foundation, every layout stylesheet and every component stylesheet, grouped under the same block comments components.css used, plus a Layout block. It also directly imports the three files nothing imported by name before (layout/spacing.css, layout/visibility.css, components/selection.css), so every file under components/ and layout/ is now one @import away. Every test that read a block of components.css or layout.css now reads the matching block of all.css through one shared helper (allCssBlock(), in tests/Pest.php so it loads for any test run) instead of repeating the same substr() search in each file. StylesheetsTest.php's shape, Tailwind-free and breakpoint checks, previously run twice (once for the foundation, once for the layout tree), now run once over the whole tree all.css reaches, since every component and layout stylesheet is plain CSS after step 36; a new test asserts all.css imports everything under components/ and layout/ exactly once. The Workbench imports all.css in place of layout.css and components.css. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
94 lines
5.1 KiB
PHP
94 lines
5.1 KiB
PHP
<?php
|
|
|
|
use Livewire\Component;
|
|
use Livewire\Livewire;
|
|
|
|
it('draws the list pane and the detail pane, each focusable as a whole, with nothing selected', function () {
|
|
$html = (string) $this->blade(<<<'BLADE'
|
|
<x-list-detail>
|
|
<x-slot:list><ul>LIST</ul></x-slot:list>
|
|
<x-slot:detail><article>DETAIL</article></x-slot:detail>
|
|
</x-list-detail>
|
|
BLADE);
|
|
|
|
expect(layoutRoot($html))->toMatchArray([
|
|
'<' => 'div',
|
|
'data-md-list-detail' => 'data-md-list-detail',
|
|
'x-data' => 'materialListDetail( null )',
|
|
'x-modelable' => 'selected',
|
|
'x-bind:data-md-selected' => "hasSelection ? '' : null",
|
|
'wire:ignore.self' => '',
|
|
])->not->toHaveKey('data-md-selected')
|
|
->and($html)->toMatch('/<div\s+data-md-list-detail-pane="list"\s+tabindex="-1"\s+x-ref="list"\s+x-on:focusin="remember\(\$event\)"\s+x-on:click="remember\(\$event\)"\s*><ul>LIST<\/ul><\/div>/')
|
|
->toMatch('/<div data-md-list-detail-pane="detail" tabindex="-1" x-ref="detail">.*<article>DETAIL<\/article>\s*<\/div>\s*<\/div>\s*$/s');
|
|
});
|
|
|
|
it('renders the pane that is selected from the first paint, given once or bound to Livewire', function () {
|
|
expect(layoutRoot((string) $this->blade('<x-list-detail :selected="7" />')))->toMatchArray(['data-md-selected' => 'data-md-selected', 'x-data' => 'materialListDetail( 7 )'])
|
|
->and(layoutRoot((string) $this->blade('<x-list-detail selected="" />')))->not->toHaveKey('data-md-selected')
|
|
->and(layoutRoot((string) $this->blade('<x-list-detail :selected="false" />')))->not->toHaveKey('data-md-selected')
|
|
->and(layoutRoot((string) $this->blade('<x-list-detail :selected="0" />')))->toHaveKey('data-md-selected');
|
|
|
|
Livewire::component('list-detail-render-probe', new class extends Component
|
|
{
|
|
public ?int $messageId = 3;
|
|
|
|
public function render(): string
|
|
{
|
|
return '<div><x-list-detail wire:model.live="messageId"><x-slot:list>LIST</x-slot:list><x-slot:detail>DETAIL</x-slot:detail></x-list-detail></div>';
|
|
}
|
|
});
|
|
|
|
$probe = Livewire::test('list-detail-render-probe');
|
|
$html = $probe->html();
|
|
|
|
// The server renders the pane from the property, and Alpine takes the property over; the
|
|
// binding stays off the element, where Livewire would look for an input.
|
|
expect($html)->toContain('data-md-selected')
|
|
->toMatch("/x-data=\"materialListDetail\\(\\s*window\\.Livewire\\.find\\('[^']+'\\)\\.entangle\\('messageId'\\)\\.live\\s*\\)\"/")
|
|
->not->toContain('x-modelable')
|
|
->not->toContain('wire:model');
|
|
|
|
$probe->set('messageId', null);
|
|
|
|
expect($probe->html())->not->toMatch('/data-md-list-detail[^>]*data-md-selected/');
|
|
});
|
|
|
|
it('draws its own back row above the detail, unless the detail brings one', function () {
|
|
$own = (string) $this->blade('<x-list-detail><x-slot:list>LIST</x-slot:list><x-slot:detail>DETAIL</x-slot:detail></x-list-detail>');
|
|
$pane = (string) $this->blade(<<<'BLADE'
|
|
<x-list-detail>
|
|
<x-slot:list>LIST</x-slot:list>
|
|
<x-slot:detail><x-pane title="Message" back>DETAIL</x-pane></x-slot:detail>
|
|
</x-list-detail>
|
|
BLADE);
|
|
|
|
expect($own)->toMatch('/x-ref="detail">\s*<div data-md-list-detail-back-row>\s*<span data-md-list-detail-back>\s*<button[^>]*aria-label="Back"[^>]*x-on:click="back\(\)"/s')
|
|
->and(substr_count($pane, 'data-md-list-detail-back'))->toBe(1)
|
|
->and($pane)->not->toContain('data-md-list-detail-back-row');
|
|
});
|
|
|
|
it('takes the element, the visibility props and the caller\'s class and style', function () {
|
|
expect(layoutRoot((string) $this->blade('<x-list-detail as="section" hide-below="medium" class="inbox" style="min-height: 30rem" x-model="chosen" />')))
|
|
->toMatchArray([
|
|
'<' => 'section',
|
|
'data-md-hide-below' => 'medium',
|
|
'class' => 'inbox',
|
|
'style' => 'min-height: 30rem;',
|
|
'x-model' => 'chosen',
|
|
'x-modelable' => 'selected',
|
|
]);
|
|
});
|
|
|
|
it('shows one pane below expanded and both from it, at M3\'s fixed pane widths', function () {
|
|
$css = (string) file_get_contents(__DIR__.'/../../../resources/css/layout/list-detail.css');
|
|
|
|
expect($css)->toContain('@layer material.layout')
|
|
->toContain("@media (width >= 840px) {\n grid-template-columns: 360px minmax(0, 1fr);\n column-gap: var(--md-sys-measurement-space300);")
|
|
->toContain("@media (width >= 1200px) {\n grid-template-columns: 412px minmax(0, 1fr);")
|
|
->toContain("@media (width < 840px) {\n [data-md-list-detail]:not([data-md-selected]) > [data-md-list-detail-pane='detail'],\n [data-md-list-detail][data-md-selected] > [data-md-list-detail-pane='list'] {\n display: none;")
|
|
->toContain('[data-md-list-detail-back] [data-md-icon]:dir(rtl)')
|
|
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './layout/list-detail.css';")
|
|
->and((string) file_get_contents(__DIR__.'/../../../resources/js/material.js'))->toContain("import './layout.js'");
|
|
});
|