SealShare's only navigation is a floating toolbar at place="bottom". In 1.x it set --material-bottom-bar to lift the snackbar over it; in 2.0.0 the toolbar reads that variable to place itself, so an application can no longer set it, and the snackbar landed on the toolbar. M3 nudges a snackbar "upward to avoid overlapping FABs/docked toolbars" and never puts one in front of navigation. A bottom-placed toolbar now publishes --material-bottom-toolbar, the distance from the window's bottom edge to its top: floating 64px (as tall as an md or lg FAB beside it), docked 64px plus the safe area it pads, rounded docked floating from 840px. It is declared on :root and again on the scaffold's root, the only place its formula sees the bar. The snackbar clears whichever of the bar and the toolbar reaches higher, and a page pads its end with it. Two Chromium tests, both failing on the old formula: a docked toolbar on a scaffold's navigation bar, and a floating toolbar without a scaffold (plan step 46). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
97 lines
5.2 KiB
PHP
97 lines
5.2 KiB
PHP
<?php
|
|
|
|
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
|
|
|
it('hosts the snackbar queue, kept across wire:navigate', function () {
|
|
$html = (string) $this->blade('<x-toast />');
|
|
|
|
expect($html)
|
|
->toContain('x-persist="material-toast"')
|
|
->toContain('x-data="materialSnackbar"')
|
|
->toContain('data-md-toast')
|
|
->toContain('data-md-position="bottom"')
|
|
->and(ComponentStylesheet::read('toast')->declarations('[data-md-toast-snackbar]'))->toMatchArray([
|
|
'background-color' => 'var(--md-sys-color-inverse-surface)',
|
|
'color' => 'var(--md-sys-color-inverse-on-surface)',
|
|
]);
|
|
});
|
|
|
|
it('draws no state icon, and reaches 48px from its 40px controls', function () {
|
|
$html = (string) $this->blade('<x-toast />');
|
|
$css = ComponentStylesheet::read('toast');
|
|
|
|
// M3 tells you to avoid an icon in a snackbar; the type is left to pick the announcement role.
|
|
expect(substr_count($html, '<svg'))->toBe(1)
|
|
->and($html)->not->toContain('text-inverse-success')
|
|
->toContain('data-md-toast-action')
|
|
->toContain('data-md-toast-dismiss')
|
|
// 48px, from the foundation's md-touch-target class, not a copy of its own.
|
|
->toMatch('/class="md-state-layer md-focus-ring md-touch-target" data-md-toast-action/')
|
|
->toMatch('/class="md-state-layer md-focus-ring md-touch-target" data-md-toast-dismiss/')
|
|
->and($css->declarations(':is([data-md-toast-action], [data-md-toast-dismiss])'))
|
|
->toHaveKey('border-radius', 'var(--md-sys-shape-corner-full)')
|
|
->and($css->has(':is([data-md-toast-action], [data-md-toast-dismiss])::after'))->toBeFalse()
|
|
->and($css->declarations('[data-md-toast-dismiss]'))->toMatchArray([
|
|
'width' => 'var(--md-sys-measurement-space500)',
|
|
'height' => 'var(--md-sys-measurement-space500)',
|
|
]);
|
|
});
|
|
|
|
it('grows to M3\'s two-line height, and wraps the action below on a compact window', function () {
|
|
$html = (string) $this->blade('<x-toast />');
|
|
$css = ComponentStylesheet::read('toast');
|
|
|
|
// SnackbarTokens: 48dp for one line, 68dp for two — a description is that second line.
|
|
expect($html)
|
|
->toContain('x-bind:data-md-two-line="current.description ? \'\' : null"')
|
|
->toContain('x-bind:data-md-toast-wrap="current.description && current.action ? \'\' : null"')
|
|
->and($css->declarations('[data-md-toast-snackbar]'))->toHaveKey('min-height', 'var(--md-sys-measurement-space600)')
|
|
->and($css->declarations('[data-md-toast-snackbar][data-md-two-line]'))->toBe(['min-height' => '68px'])
|
|
// The text grows from a zero basis, so a long title shrinks beside the action rather than
|
|
// wrapping it onto a line of its own where there is room for both.
|
|
->and($css->declarations('[data-md-toast-content]'))->toHaveKey('flex', '1')
|
|
->and($css->declarations('[data-md-toast-content][data-md-toast-wrap]', ['@media (width < 600px)']))->toBe(['flex-basis' => '100%'])
|
|
->and($css->declarations('[data-md-toast-action]'))->toHaveKey('height', 'var(--md-sys-measurement-space500)');
|
|
});
|
|
|
|
it('can sit at the start, growing its margin from medium', function () {
|
|
$css = ComponentStylesheet::read('toast');
|
|
|
|
expect((string) $this->blade('<x-toast position="bottom-start" />'))->toContain('data-md-position="bottom-start"')
|
|
->and($css->declarations("[data-md-toast][data-md-position='bottom-start']"))->toBe(['justify-content' => 'flex-start'])
|
|
->and($css->declarations("[data-md-toast][data-md-position='bottom-start']", ['@media (width >= 600px)']))->toBe([
|
|
'inset-inline-start' => 'var(--md-sys-measurement-space300)',
|
|
]);
|
|
});
|
|
|
|
it('falls back to bottom for an unknown position', function () {
|
|
expect((string) $this->blade('<x-toast position="top" />'))->toContain('data-md-position="bottom"');
|
|
});
|
|
|
|
it('marks the snackbar and its action for tests and styling', function () {
|
|
$html = (string) $this->blade('<x-toast />');
|
|
|
|
expect($html)
|
|
->toMatch('/<div\s[^>]*\bdata-md-toast-snackbar\b/')
|
|
->toMatch('/<button\s[^>]*\bdata-md-toast-action\b[^>]*x-on:click="act\(\)"/')
|
|
->toMatch('/<button\s[^>]*\bdata-md-toast-dismiss\b[^>]*x-on:click="dismiss\(\)"/');
|
|
});
|
|
|
|
it('keeps the live region in the page, polite, around the snackbar that comes and goes', function () {
|
|
$html = (string) $this->blade('<x-toast />');
|
|
|
|
expect($html)
|
|
->toMatch('/<div\s[^>]*x-data="materialSnackbar"[^>]*aria-live="polite"/')
|
|
->toContain('aria-atomic="true"')
|
|
->toContain("x-bind:role=\"current && (current.type === 'error' || current.type === 'warning') ? 'alert' : 'status'\"")
|
|
->and(substr_count($html, 'aria-live'))->toBe(1);
|
|
});
|
|
|
|
it('lifts above a bottom bar or a toolbar placed at the bottom, publishing its own height for a FAB to clear', function () {
|
|
// Whichever reaches higher: M3 nudges a snackbar clear of a docked toolbar and never puts one
|
|
// in front of navigation (toolbar.css publishes --material-bottom-toolbar).
|
|
expect(ComponentStylesheet::read('toast')->declarations('[data-md-toast]'))->toMatchArray([
|
|
'bottom' => 'calc(max(var(--material-bottom-bar, 0px), var(--material-bottom-toolbar, 0px)) + var(--md-sys-measurement-space200))',
|
|
]);
|
|
});
|