Plan step 36. The paginators put md-state-layer, md-focus-ring and md-touch-target on their live steps, where the rest of the actions and communication group (button, FAB, menu item, snackbar, alert) writes no class list and carries those declarations keyed on its own hooks, as the plan's data-md-* rule and the brief's "text classes, nothing else" ask. pagination.css now draws them on a link or button step, the layer hidden while Livewire disables previous and next; the views render no class at all, which the render tests pin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
128 lines
7.1 KiB
PHP
128 lines
7.1 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/components.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', 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" 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')->declarations(':is(a, button)[data-md-pagination-step]::after'))->toMatchArray([
|
||
'min-width' => 'var(--md-sys-measurement-space600)',
|
||
'min-height' => 'var(--md-sys-measurement-space600)',
|
||
])
|
||
->and(ComponentStylesheet::read('pagination')->declarations(':is(a, button)[data-md-pagination-step]:focus-visible'))->toBe([
|
||
'outline' => '3px solid var(--md-sys-color-secondary)',
|
||
'outline-offset' => '2px',
|
||
]);
|
||
});
|
||
|
||
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>')
|
||
->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')
|
||
->not->toContain('class="');
|
||
|
||
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')
|
||
->not->toContain('class="');
|
||
});
|
||
|
||
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/components.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/components.css'))->toContain("@import './components/pagination.css';");
|
||
});
|