blade('')) ->toContain('role="group"') ->toContain('aria-label="Views"') ->toContain('data-button-group="standard"') ->toContain('data-size="md"') ->toContain('gap-2') ->and((string) $this->blade('')) ->toContain('gap-3'); }); it('connects buttons 2px apart', function () { expect((string) $this->blade('')) ->toContain('data-button-group="connected"') ->toContain('gap-0.5'); }); it('squares a group, and a choice drawn as one', function () { // M3 lists "Default shape | Round, square" as a button-group configuration; the shape covers // every button in the group, so it need not be written on each one. expect((string) $this->blade('')) ->toContain('data-shape="square"') ->and((string) $this->blade('')) ->toContain('data-button-group="connected"') ->toContain('data-shape="square"') ->and((string) $this->blade('')) ->toContain('data-shape="round"') ->and((string) $this->blade('')) ->toContain('data-shape="square"') ->and((string) $this->blade('')) ->toContain('data-shape="round"'); }); it('takes over aria-pressed for a group that carries a selection', function () { $html = (string) $this->blade(''); expect($html) ->toContain('data-selection="single"') ->toContain('data-selection-required') ->toContain('multiple: false') ->toContain('required: true') ->toContain('value: null') ->toContain('x-on:click="press($event)"') // No model: the group takes the state from the buttons' own aria-pressed and goes on. ->toContain('x-modelable="value"') ->and((string) $this->blade('')) ->toContain('multiple: true') ->toContain('required: false') ->toContain('value: []') ->and((string) $this->blade('')) ->not->toContain('data-selection') ->not->toContain('press($event)'); }); it('entangles a selection group with the property it binds, and keeps wire:model off the div', function () { $component = new class extends Component { public string $view = 'week'; public function render(): string { return <<<'BLADE'
BLADE; } }; $html = Livewire::test($component)->assertSet('view', 'week')->html(); expect($html) ->toContain('data-selection="single"') ->toMatch('/value: window\.Livewire\.find\(/') ->and(substr_count($html, 'wire:model.live="view"'))->toBe(0); }); it('never wraps a group onto a second line', function () { expect((string) $this->blade('')) ->not->toContain('flex-wrap'); }); it('marks icon buttons, which keep their width when a group is pressed', function () { expect((string) $this->blade(''))->toContain('data-icon-button') ->and((string) $this->blade(''))->not->toContain('data-icon-button'); }); it('draws a choice as connected radios', function () { $html = (string) $this->blade(<<<'BLADE' BLADE); expect($html) ->toContain('Theme') ->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, '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('', ['options' => $options])) ->toContain('touch-target min-w-12') ->and((string) $this->blade('', ['options' => $options])) ->not->toContain('touch-target') ->toContain('min-w-0'); }); it('draws several choices as checkboxes', function () { expect((string) $this->blade('')) ->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'
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('', ['options' => $options])) ->toContain('

No elevation data here

') ->and((string) $this->blade('', ['options' => $options])) ->toContain('

No elevation data here

'); expect((string) $this->withViewErrors(['terrain' => 'Pick a terrain.'])->blade('', ['options' => $options])) ->toContain('

Pick a terrain.

') ->not->toContain('No elevation data here') ->not->toContain('text-warning'); });