Files
livewire-material/tests/Feature/Components/ToastTest.php
T
Andreas Reinhold / reiniandClaude Opus 5 44ae9ecea4 Pin the two-line snackbar, and reach it with Alt+G
Plan step 22, actions.md § Missing (two-line snackbar height, snackbar
keyboard shortcut): the container grew organically from `min-h-12` rather
than sitting at SnackbarTokens.TwoLinesContainerHeight, and there was no
way for a keyboard to reach an actioned snackbar at all.

A `description` is M3's second line, so the snackbar is pinned to 68px
whenever one is there, and below `medium` a two-line snackbar with an
action wraps the action under the text — M3's "two lines with longer
action" configuration. Alt+G, the shortcut M3 suggests for the web, moves
the focus to a snackbar that carries an action from wherever the page had
it; `event.code`, because Alt rewrites `event.key` on some layouts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-14 06:45:06 +02:00

57 lines
2.4 KiB
PHP

<?php
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('bg-inverse-surface')
->toContain('justify-center');
});
it('draws no state icon, and reaches 48px from its 40px controls', function () {
$html = (string) $this->blade('<x-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')
->not->toContain('opacity-80')
->toContain('data-toast-action class="state-layer focus-ring touch-target')
->toContain('state-layer focus-ring touch-target inline-flex size-10');
});
it('grows to M3\'s two-line height, and wraps the action below on a compact window', function () {
$html = (string) $this->blade('<x-toast />');
// SnackbarTokens: 48dp for one line, 68dp for two — a description is that second line.
expect($html)
->toContain('x-bind:class="current.description ? \'min-h-17\' : \'min-h-12\'"')
->toContain('flex-wrap')
// M3's "two lines with longer action": the action leaves the text's line below `medium`.
->toContain('x-bind:class="current.description && current.action ? \'max-medium:basis-full\' : \'\'"')
->toContain('touch-target ms-auto h-10');
});
it('can sit at the start', function () {
expect((string) $this->blade('<x-toast position="bottom-start" />'))->toContain('justify-start');
});
it('marks the snackbar and its action for tests and styling', function () {
$html = (string) $this->blade('<x-toast />');
expect($html)
->toMatch('/<div\s[^>]*\bdata-toast\b/')
->toMatch('/<button\s[^>]*\bdata-toast-action\b[^>]*x-on:click="act\(\)"/');
});
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);
});