Files
livewire-material/tests/Browser/DataTest.php
T
surtic86andClaude Opus 5 1b38b39a47
tests / feature (8.4) (push) Successful in 1m51s
tests / feature (8.5) (push) Successful in 1m57s
tests / browser (chrome, chromium) (push) Successful in 9m7s
tests / browser (firefox, firefox) (push) Failing after 15m45s
tests / browser (safari, webkit) (push) Failing after 16m52s
Stop every test page asking for a favicon nothing serves
`ready()` waits for the network to go idle, and each probe page asked for
/favicon.ico, which its route does not answer: the server threw
NotFoundHttpException on the way, and on the runner the two full-screen
date picker tests spent their whole budget inside that wait rather than
in the click Pest named. Every probe page now carries `<link rel="icon"
href="data:,">`, so the browser asks for nothing.

The showcase's own head carries it too. It ships no icon, and the 404 was
the application's to answer.

The runner takes two and a half to three times as long as a workstation,
so BROWSER_TIMEOUT there goes from 15 to 45 seconds: a page that loads
the application's Vite entries, Alpine and Livewire had no headroom left.

Feature 1159 passed; Browser 299 passed on Chrome, Firefox and WebKit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 05:30:29 +02:00

178 lines
8.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
use Illuminate\Contracts\View\View;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
use Livewire\Livewire;
use Livewire\WithPagination;
class DataProbe extends Component
{
use WithPagination;
/** @var array{column: string, direction: string} */
public array $sortBy = ['column' => 'name', 'direction' => 'asc'];
public function render(): View
{
$files = collect(range(1, 25))->map(fn (int $n): array => ['name' => sprintf('file-%02d.zip', $n), 'size' => $n * 3 % 17]);
$files = $files->sortBy($this->sortBy['column'], SORT_REGULAR, $this->sortBy['direction'] === 'desc')->values();
$page = $this->getPage();
return view('probe::data', [
'files' => new LengthAwarePaginator($files->forPage($page, 10)->values(), $files->count(), 10, $page),
]);
}
}
function dataProbe()
{
$views = sys_get_temp_dir().'/livewire-material-data-probe';
@mkdir($views);
file_put_contents($views.'/data.blade.php', <<<'BLADE'
<div style="padding: var(--md-sys-measurement-space200);">
<x-table>
<thead>
<tr>
<x-sort-header column="name" :sort-by="$sortBy" id="by-name">Name</x-sort-header>
<x-sort-header column="size" :sort-by="$sortBy" id="by-size">Size</x-sort-header>
</tr>
</thead>
<tbody>
@foreach ($files as $file)
<tr wire:key="file-{{ $file['name'] }}"><td>{{ $file['name'] }}</td><td>{{ $file['size'] }}</td></tr>
@endforeach
</tbody>
</x-table>
{{ $files->links() }}
<x-table dense id="dense-table">
<tbody>
<tr><td>dense-a.zip</td><td>1</td></tr>
<tr aria-selected="true"><td>dense-b.zip</td><td>2</td></tr>
</tbody>
</x-table>
</div>
BLADE);
app('view')->addNamespace('probe', $views);
Livewire::component('data-probe', DataProbe::class);
Route::middleware('web')->get('/data-probe', fn () => Blade::render(<<<'BLADE'
<!DOCTYPE html>
<html>
<head>
<link rel="icon" href="data:,">
<x-theme-script />
@vite(config('livewire-material.showcase.vite'))
@livewireStyles
</head>
<body style="background-color: var(--md-sys-color-surface);">
<livewire:data-probe />
@livewireScripts
</body>
</html>
BLADE));
return ready(visit('/data-probe'));
}
it('sorts by a column and flips the direction on a second press', function () {
$first = "document.querySelector('tbody tr td').textContent.trim()";
$page = dataProbe()
->assertAttribute('#by-name', 'aria-sort', 'ascending')
->assertScript("{$first} === 'file-01.zip'");
$page->click('#by-name button')
->assertAttribute('#by-name', 'aria-sort', 'descending')
->assertScript("{$first} === 'file-25.zip'");
$page->click('#by-size button')
->assertAttribute('#by-size', 'aria-sort', 'ascending')
->assertScript("! document.querySelector('#by-name').hasAttribute('aria-sort')");
});
it('pages through Livewire results and marks the current page', function () {
$page = dataProbe()
->assertSee('110 of 25')
->assertAttribute('[data-md-pagination] [aria-current="page"]', 'aria-current', 'page')
->click('[data-md-pagination] button[aria-label="Go to page 3"]')
->assertSee('2125 of 25')
->assertSeeIn('[data-md-pagination] [aria-current="page"]', '3');
$page->click('[data-md-pagination] button[aria-label="Previous"]')
->assertSee('1120 of 25');
$page->resize(400, 800)
->assertSee('Page 2 of 3');
});
it('draws 52px rows, and 36px ones only in a table that asks to be dense', function () {
$row = fn (string $table): string => "document.querySelector('{$table} tbody tr').getBoundingClientRect().height";
dataProbe()
->assertScript("Math.abs(({$row('[data-md-table]:not([data-md-dense])')}) - 52) <= 1")
->assertScript("Math.abs(({$row('#dense-table')}) - 36) <= 1")
->assertScript("getComputedStyle(document.querySelector('#dense-table tr[aria-selected=\"true\"]')).backgroundColor !== 'rgba(0, 0, 0, 0)'")
->assertScript("getComputedStyle(document.querySelector('[data-md-table] thead th')).borderBottomWidth === '1px'");
});
it('lets the sort button be pressed anywhere in its 48px target', function () {
dataProbe()
->assertScript("(() => { const button = document.querySelector('#by-size [data-md-sort-header-button]'); const after = getComputedStyle(button, '::after'); return button.getBoundingClientRect().height < 48 && after.minHeight === '48px' && after.minWidth === '48px'; })()")
->assertScript("(() => { const button = document.querySelector('#by-size [data-md-sort-header-button]'); const box = button.getBoundingClientRect(); const probe = document.elementFromPoint(box.left + box.width / 2, box.top + box.height / 2 - 22); return probe === button; })()");
});
it('draws each page step 40px and lets it be pressed anywhere in its 48px target', function () {
dataProbe()
->assertScript("(() => { const step = document.querySelector('[data-md-pagination] button[aria-label=\"Go to page 2\"]'); const box = step.getBoundingClientRect(); return box.width === 40 && box.height === 40 && document.elementFromPoint(box.left + box.width / 2, box.top - 3) === step; })()")
->assertScript("getComputedStyle(document.querySelector('[data-md-pagination] [aria-current=\"page\"]')).backgroundColor !== 'rgba(0, 0, 0, 0)'");
});
it('shows a page step\'s keyboard focus ring, from md-focus-ring', function () {
$step = "document.querySelector('[data-md-pagination] button[aria-label=\"Go to page 2\"]')";
$page = dataProbe();
// A key press first puts the browser in keyboard modality, as a keyboard user would be —
// Firefox only counts a scripted focus as :focus-visible after one.
$page->keys('#by-name [data-md-sort-header-button]', 'Tab');
$page->script("{$step}.focus()");
$page->assertScript("document.activeElement === {$step}")
->assertScript("getComputedStyle({$step}).outlineStyle === 'solid'")
->assertScript("getComputedStyle({$step}).outlineWidth === '3px'");
});
it('keeps a selected table row\'s fill under the hover and focus tint', function () {
$page = ready(visit('/material/data'));
$selected = "document.querySelector('#data tr[data-md-list-row][aria-selected=\"true\"]')";
$plain = "document.querySelector('#data tr[data-md-list-row]:not([aria-selected=\"true\"])')";
$restBackground = $page->script("getComputedStyle({$selected}).backgroundColor");
$page->hover('#data tr[data-md-list-row][aria-selected="true"]');
$selectedHoverBackground = $page->script("getComputedStyle({$selected}).backgroundColor");
$page->hover('#data tr[data-md-list-row]:not([aria-selected="true"]) >> nth=0');
$plainHoverBackground = $page->script("getComputedStyle({$plain}).backgroundColor");
// The hover tint mixes over the row's own fill (`--md-list-row-fill`, table.css/list-item.css):
// the selected row's secondary-container stays under it, so its hover colour differs from both
// its own resting colour and a plain row's hover, which has no fill to keep.
expect($selectedHoverBackground)->not->toBe($restBackground)
->and($selectedHoverBackground)->not->toBe($plainHoverBackground);
$page->script("{$selected}.querySelector('[data-md-list-open]').focus()");
$selectedFocusBackground = $page->script("getComputedStyle({$selected}).backgroundColor");
expect($selectedFocusBackground)->not->toBe($restBackground)
->and($selectedFocusBackground)->not->toBe($selectedHoverBackground);
});