Files
livewire-material/tests/Feature/Components/ButtonGroupTest.php
T
Andreas ReinholdandClaude Opus 5 573d94fe6e Give the xs button M3's 16dp padding, and stop groups wrapping
ButtonXSmallTokens puts 16dp either side of an xs label, where the button, the
<x-group> segment and --group-pad all wrote 12px; the variants table calls 16dp
the Expressive value twice over. A standard button group also dropped onto a
second line on a narrow window, which M3 forbids — and the press expansion only
ever reaches a neighbour on the same line. Plan step 18, actions.md ACT-09,
ACT-10.

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

114 lines
4.9 KiB
PHP

<?php
use Livewire\Component;
use Livewire\Livewire;
it('groups buttons with the spacing of their size', function () {
expect((string) $this->blade('<x-button-group label="Views" size="md"><x-button label="Day" /></x-button-group>'))
->toContain('role="group"')
->toContain('aria-label="Views"')
->toContain('data-button-group="standard"')
->toContain('data-size="md"')
->toContain('gap-2')
->and((string) $this->blade('<x-button-group><x-button label="Day" /></x-button-group>'))
->toContain('gap-3');
});
it('connects buttons 2px apart', function () {
expect((string) $this->blade('<x-button-group connected><x-button label="Day" /></x-button-group>'))
->toContain('data-button-group="connected"')
->toContain('gap-0.5');
});
it('never wraps a group onto a second line', function () {
expect((string) $this->blade('<x-button-group><x-button label="Day" /></x-button-group>'))
->not->toContain('flex-wrap');
});
it('marks icon buttons, which keep their width when a group is pressed', function () {
expect((string) $this->blade('<x-button icon="format_bold" aria-label="Bold" />'))->toContain('data-icon-button')
->and((string) $this->blade('<x-button label="Bold" />'))->not->toContain('data-icon-button');
});
it('draws a choice as connected radios', function () {
$html = (string) $this->blade(<<<'BLADE'
<x-group label="Theme" name="theme" x-model="theme" :options="[
['id' => 'light', 'name' => 'Light', 'icon' => 'light_mode'],
['id' => 'dark', 'name' => 'Dark', 'disabled' => true],
]" />
BLADE);
expect($html)
->toContain('<legend class="mb-2 type-label-lg text-on-surface-variant">Theme</legend>')
->toContain('data-button-group="connected"')
->toContain('type="radio"')
->toContain('name="theme"')
->toContain('x-model="theme"')
->toContain('value="light"')
->toContain('has-checked:bg-secondary has-checked:text-on-secondary')
->toContain('disabled')
->and(substr_count($html, '<svg'))->toBe(1);
});
it('keeps a 48px target and a 48px minimum width on the smallest connected segments', function () {
$options = [['id' => 'a', 'name' => 'A']];
expect((string) $this->blade('<x-group size="sm" name="x" :$options />', ['options' => $options]))
->toContain('touch-target min-w-12')
->and((string) $this->blade('<x-group size="md" name="x" :$options />', ['options' => $options]))
->not->toContain('touch-target')
->toContain('min-w-0');
});
it('draws several choices as checkboxes', function () {
expect((string) $this->blade('<x-group name="days" multiple variant="outlined" :options="[[\'id\' => \'mon\', \'name\' => \'Mon\']]" />'))
->toContain('type="checkbox"')
->toContain('name="days[]"')
->toContain('has-checked:bg-inverse-surface');
});
it('binds to a Livewire property and shows its validation message', function () {
$component = new class extends Component
{
public string $theme = 'dark';
public function save(): void
{
$this->validate(['theme' => 'in:light'], ['theme.in' => 'Pick light.']);
}
public function render(): string
{
return <<<'BLADE'
<div>
<x-group wire:model="theme" hint="How it looks" :options="[['id' => 'light', 'name' => 'Light'], ['id' => 'dark', 'name' => 'Dark']]" />
</div>
BLADE;
}
};
Livewire::test($component)
->assertSeeHtml('wire:model="theme"')
->assertSee('How it looks')
->set('theme', 'light')
->assertSet('theme', 'light')
->set('theme', 'dark')
->call('save')
->assertSee('Pick light.')
->assertDontSee('How it looks');
});
it('adds hint-class to the hint, which a validation message still replaces', function () {
$options = [['id' => 'flat', 'name' => 'Flat'], ['id' => 'hilly', 'name' => 'Hilly']];
expect((string) $this->blade('<x-group wire:model="terrain" hint="No elevation data here" hint-class="text-warning" :$options />', ['options' => $options]))
->toContain('<p class="mt-1 type-body-sm [:where(&amp;)]:text-on-surface-variant text-warning">No elevation data here</p>')
->and((string) $this->blade('<x-group wire:model="terrain" hint="No elevation data here" :$options />', ['options' => $options]))
->toContain('<p class="mt-1 type-body-sm text-on-surface-variant">No elevation data here</p>');
expect((string) $this->withViewErrors(['terrain' => 'Pick a terrain.'])->blade('<x-group wire:model="terrain" hint="No elevation data here" hint-class="text-warning" :$options />', ['options' => $options]))
->toContain('<p class="mt-1 type-body-sm text-error">Pick a terrain.</p>')
->not->toContain('No elevation data here')
->not->toContain('text-warning');
});