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
125 lines
7.3 KiB
PHP
125 lines
7.3 KiB
PHP
<?php
|
||
|
||
use Illuminate\Pagination\LengthAwarePaginator;
|
||
use Illuminate\Pagination\Paginator;
|
||
use Livewire\Component;
|
||
use Livewire\Livewire;
|
||
use Livewire\WithPagination;
|
||
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
||
|
||
it('marks a table for its styles and takes a size', function () {
|
||
expect((string) $this->blade('<x-table class="min-w-160"><tbody><tr><td>a</td></tr></tbody></x-table>'))
|
||
->toContain('<table data-md-table data-md-size="sm" class="min-w-160">')
|
||
->and((string) $this->blade('<x-table size="xs" />'))->toContain('data-md-size="xs"')
|
||
->and((string) $this->blade('<x-table size="huge" />'))->toContain('data-md-size="sm"');
|
||
});
|
||
|
||
it('tightens a table only when the caller asks for it', function () {
|
||
expect((string) $this->blade('<x-table />'))->not->toContain('data-md-dense')
|
||
->and((string) $this->blade('<x-table dense />'))->toContain('data-md-dense');
|
||
});
|
||
|
||
it('draws 52px rows, 36px dense ones, from its stylesheet in the components layer', function () {
|
||
$css = (string) file_get_contents(__DIR__.'/../../../resources/css/components/table.css');
|
||
|
||
expect($css)->toContain('@layer material.components')
|
||
->toMatch('/\\[data-md-table\\] \\{\\s*--cell-x: 12px;\\s*--cell-y: var\\(--md-sys-measurement-space200\\);/')
|
||
->toMatch('/\\[data-md-table\\]\\[data-md-dense\\] \\{\\s*--cell-y: var\\(--md-sys-measurement-space100\\);/')
|
||
->toContain('[data-md-table] :where(tbody tr[aria-selected=\'true\'])')
|
||
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/table.css';")
|
||
->and((string) file_get_contents(__DIR__.'/../../../resources/css/tailwind.css'))->not->toContain('table.css');
|
||
});
|
||
|
||
it('sorts by its column, ascending first and then flipping', function () {
|
||
expect((string) $this->blade('<x-sort-header column="size" :sort-by="[\'column\' => \'name\', \'direction\' => \'asc\']">Size</x-sort-header>'))
|
||
->not->toContain('aria-sort')
|
||
->toContain('wire:click="$set(\'sortBy\', {"column":"size","direction":"asc"})"')
|
||
->and((string) $this->blade('<x-sort-header column="size" model="order" :sort-by="[\'column\' => \'size\', \'direction\' => \'asc\']" class="text-end">Size</x-sort-header>'))
|
||
->toContain('aria-sort="ascending"')
|
||
->toMatch('/<th data-md-sort-header\\s+data-md-active aria-sort="ascending"\\s+class="text-end">/')
|
||
->toContain('$set(\'order\', {"column":"size","direction":"desc"})')
|
||
->and((string) $this->blade('<x-sort-header column="size" :sort-by="[\'column\' => \'size\', \'direction\' => \'desc\']">Size</x-sort-header>'))
|
||
->toContain('aria-sort="descending"')
|
||
->toContain('"direction":"asc"');
|
||
});
|
||
|
||
it('gives the sort button and every page control a 48px target, from the foundation\'s classes', function () {
|
||
$pages = new LengthAwarePaginator(range(1, 10), 95, 10, 3, ['path' => '/shares']);
|
||
|
||
expect((string) $this->blade('<x-sort-header column="size" :sort-by="[]">Size</x-sort-header>'))
|
||
->toMatch('/<button(?=[^>]*data-md-sort-header-button)(?=[^>]*class="md-focus-ring md-touch-target")[^>]*>/')
|
||
->toContain('--md-icon-size: 16px')
|
||
->not->toContain('data-md-active')
|
||
->and((string) $pages->links())
|
||
->toContain('<a href="/shares?page=4" class="md-state-layer md-focus-ring md-touch-target" data-md-pagination-step data-md-pagination-page aria-label="Go to page 4">')
|
||
->and(ComponentStylesheet::read('pagination')->declarations('[data-md-pagination-step]'))->toMatchArray([
|
||
'width' => 'var(--md-sys-measurement-space500)',
|
||
'height' => 'var(--md-sys-measurement-space500)',
|
||
])
|
||
->and(ComponentStylesheet::read('pagination')->has(':is(a, button)[data-md-pagination-step]::after'))->toBeFalse()
|
||
->and(ComponentStylesheet::read('pagination')->has(':is(a, button)[data-md-pagination-step]:focus-visible'))->toBeFalse();
|
||
});
|
||
|
||
it('draws Laravel\'s paginators in M3', function () {
|
||
$pages = new LengthAwarePaginator(range(1, 10), 95, 10, 3, ['path' => '/shares']);
|
||
|
||
expect((string) $pages->links())
|
||
->toContain('data-md-pagination')
|
||
->toContain('<span data-md-pagination-compact>Page 3 of 10</span>')
|
||
->toContain('<span data-md-pagination-range>21–30 of 95</span>')
|
||
// The current page is a plain span, not a live step: it carries no interaction class.
|
||
->toContain('<span aria-current="page" data-md-pagination-step data-md-pagination-page>3</span>')
|
||
->toContain('href="/shares?page=2" rel="prev"')
|
||
->toContain('aria-label="Go to page 4"')
|
||
->toContain('data-md-mirror-rtl')
|
||
->not->toContain('text-gray')
|
||
// A live step (a link or a button) carries only the foundation's interaction classes.
|
||
->not->toMatch('/class="(?!md-state-layer md-focus-ring md-touch-target")/');
|
||
|
||
expect((string) (new Paginator(range(1, 11), 10, 2, ['path' => '/shares']))->links())
|
||
->toContain('data-md-pagination')
|
||
->toContain('href="/shares?page=1"')
|
||
->toContain('href="/shares?page=3"')
|
||
->toContain('Previous');
|
||
});
|
||
|
||
it('draws Livewire\'s paginators in M3, wired to its page actions', function () {
|
||
Livewire::component('paging-probe', new class extends Component
|
||
{
|
||
use WithPagination;
|
||
|
||
public function render(): string
|
||
{
|
||
return <<<'BLADE'
|
||
<div>
|
||
{{ (new \Illuminate\Pagination\LengthAwarePaginator(range(1, 10), 95, 10, $this->getPage(), ['path' => '/'])) ->links() }}
|
||
</div>
|
||
BLADE;
|
||
}
|
||
});
|
||
|
||
expect(Livewire::test('paging-probe')->html())
|
||
->toContain('data-md-pagination')
|
||
->toContain('data-md-pagination-page')
|
||
->toContain('wire:click="gotoPage(2, \'page\')"')
|
||
->toContain('wire:click="nextPage(\'page\')"')
|
||
->not->toContain('text-gray')
|
||
// A live step (a link or a button) carries only the foundation's interaction classes.
|
||
->not->toMatch('/class="(?!md-state-layer md-focus-ring md-touch-target")/');
|
||
});
|
||
|
||
it('draws the sort header from its stylesheet, its arrow hidden until the column sorts or is hovered', function () {
|
||
expect((string) file_get_contents(__DIR__.'/../../../resources/css/components/sort-header.css'))
|
||
->toContain('@layer material.components')
|
||
->toMatch('/\\[data-md-sort-header\\]:not\\(\\[data-md-active\\]\\) \\[data-md-sort-header-arrow\\] \\{\\s*opacity: 0;/')
|
||
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/sort-header.css';");
|
||
});
|
||
|
||
it('draws the paginators from their stylesheet, trading the numbers for "Page n of m" below 600px', function () {
|
||
expect((string) file_get_contents(__DIR__.'/../../../resources/css/components/pagination.css'))
|
||
->toContain('@layer material.components')
|
||
->toMatch('/@media \\(width < 600px\\) \\{\\s*\\[data-md-pagination-page\\],\\s*\\[data-md-pagination-range\\] \\{\\s*display: none;/')
|
||
->toMatch('/@media \\(width >= 600px\\) \\{\\s*\\[data-md-pagination-compact\\] \\{\\s*display: none;/')
|
||
->and((string) file_get_contents(__DIR__.'/../../../resources/css/all.css'))->toContain("@import './components/pagination.css';");
|
||
});
|