done = 80;
}
public function render(): string
{
return <<<'BLADE'
BLADE;
}
}
/**
* A script run against one indicator in the showcase, scrolled into view first: frames are only
* drawn while an indicator is on screen. `el` is its root; the script's value is the result.
* Pest retries a failing assertion, and gives each attempt a second: keep scripts well inside it.
*/
function onProgress(string $label, string $body): string
{
return << {
const el = document.querySelector('#progress [aria-label="{$label}"]')
el.scrollIntoView({ block: 'center' })
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
const active = () => el.querySelector('path[stroke="currentColor"]')
const reach = () => { const box = active().getBBox(); return (box.x + box.width) / el.offsetWidth }
await pause(150)
{$body}
})()
JS;
}
function progressShowcase(array $options = [])
{
return visit('/material', $options)->waitForEvent('networkidle');
}
it('draws a linear indicator to its value', function () {
progressShowcase()
->assertNoJavaScriptErrors()
->assertScript(onProgress('Linear', <<<'JS'
const box = active().getBBox()
return Math.abs(box.x - 2) < 0.5 && Math.abs(box.x + box.width - el.offsetWidth * 0.4) < 1
&& el.querySelectorAll('svg svg').length === 0
JS));
});
it('draws a circular arc to its value, from 12 o\'clock', function () {
progressShowcase()->assertScript(onProgress('Circular', <<<'JS'
const path = active()
const end = path.getPointAtLength(path.getTotalLength())
const start = path.getPointAtLength(0)
const turn = (point) => (Math.atan2(point.y - el.offsetHeight / 2, point.x - el.offsetWidth / 2) * 180 / Math.PI + 450) % 360 / 360
return Math.min(turn(start), 1 - turn(start)) < 0.005 && Math.abs(turn(end) - 0.65) < 0.005
JS));
});
it('draws a circular wave as far as its value', function () {
progressShowcase()->assertScript(onProgress('Circular wavy', <<<'JS'
const path = active()
const [dash] = path.getAttribute('stroke-dasharray').split(' ').map(Number)
return path.getAttribute('visibility') === null && Math.abs(dash / (Number(path.getAttribute('pathLength')) / 2) - 0.65) < 0.001
JS));
});
it('moves to a new bound value instead of jumping', function () {
progressShowcase()
->assertScript(onProgress('Bound', <<<'JS'
const before = reach()
Alpine.$data(el).progress = 80
await pause(80)
const during = reach()
return Math.abs(before - 0.3) < 0.01 && during > before + 0.01 && during < 0.79
JS))
->wait(1)
->assertScript(onProgress('Bound', "return Math.abs(reach() - 0.8) < 0.01 && el.getAttribute('aria-valuenow') === '80'"));
});
it('turns indeterminate when a bound value is null, and back', function () {
progressShowcase()->assertScript(onProgress('Bound', <<<'JS'
Alpine.$data(el).progress = null
await pause(100)
const indeterminate = !el.hasAttribute('aria-valuenow') && !el.hasAttribute('data-value')
const first = active().getAttribute('d')
await pause(200)
const moving = active().getAttribute('d') !== first
Alpine.$data(el).progress = 50
await pause(100)
return indeterminate && moving && el.getAttribute('aria-valuenow') === '50' && Math.abs(reach() - 0.5) < 0.01
JS));
});
it('follows a value the server changes through a Livewire morph', function () {
Livewire::component('progress-morph-probe', ProgressMorphProbe::class);
Route::middleware('web')->get('/progress-morph-probe', fn () => Blade::render(<<<'BLADE'
@vite(config('livewire-material.showcase.vite'))
@livewireStyles
@livewireScripts
BLADE));
$reach = "(() => { const el = document.querySelector('[aria-label=\"Server\"]'); const box = el.querySelector('path[stroke=\"currentColor\"]').getBBox(); return (box.x + box.width) / el.offsetWidth })()";
$page = visit('/progress-morph-probe')->waitForEvent('networkidle')
->assertNoJavaScriptErrors()
->assertScript("Math.abs({$reach} - 0.2) < 0.01");
$page->click('Advance')
->wait(1.5)
->assertAttribute('[aria-label="Server"]', 'aria-valuenow', '80')
->assertScript("Math.abs({$reach} - 0.8) < 0.01")
->assertScript("document.querySelectorAll('[aria-label=\"Server\"] svg svg').length === 0");
});
it('animates while indeterminate', function () {
progressShowcase()
->assertScript(onProgress('Linear, indeterminate', <<<'JS'
const first = active().getAttribute('d')
await pause(200)
return active().getAttribute('d') !== first
JS))
->assertScript(onProgress('Circular wavy, indeterminate', <<<'JS'
const group = el.querySelector('g')
const first = group.getAttribute('transform') + active().getAttribute('stroke-dasharray')
await pause(200)
return group.getAttribute('transform') + active().getAttribute('stroke-dasharray') !== first
JS));
});
it('holds still under reduced motion', function () {
progressShowcase(['reducedMotion' => 'reduce'])
->assertScript(onProgress('Wavy, indeterminate', <<<'JS'
const first = active().getAttribute('d')
await pause(300)
return active().getAttribute('d') === first
JS))
->assertScript(onProgress('Wavy', <<<'JS'
const first = active().getAttribute('d')
await pause(300)
return active().getAttribute('d') === first
JS))
->assertScript(onProgress('Bound', <<<'JS'
Alpine.$data(el).progress = 90
await pause(50)
return Math.abs(reach() - 0.9) < 0.01
JS));
});