and($tag[0])->not->toContain('class=')
->and(ComponentStylesheet::read('button-group')->declarations(in_array($size, ['xs', 'sm'], true)
? "[data-md-button-group='standard'][data-md-size='{$size}']"
: "[data-md-button-group='standard']"))->toBe(['gap' => $gap]);
})->with([
'xs' => ['xs', '18px'],
'sm' => ['sm', '12px'],
'md' => ['md', 'var(--md-sys-measurement-space100)'],
'lg' => ['lg', 'var(--md-sys-measurement-space100)'],
'xl' => ['xl', 'var(--md-sys-measurement-space100)'],
]);
it('connects buttons 2px apart, with M3\'s inner corners', function () {
$css = ComponentStylesheet::read('button-group');
expect((string) $this->blade('
'))->toContain('data-md-button-group="connected"')
->and($css->declarations("[data-md-button-group='connected']"))->toBe(['gap' => 'var(--md-sys-measurement-space25)'])
->and($css->declarations("[data-md-button-group='connected'] > *"))->toMatchArray([
'--md-button-group-corner' => 'var(--md-button-group-inner)',
'border-start-start-radius' => 'var(--md-button-group-corner)',
])
->and($css->declarations("[data-md-button-group='connected'] > :active"))->toBe(['--md-button-group-corner' => 'var(--md-button-group-inner-pressed)'])
->and($css->declarations("[data-md-button-group='connected'] > :first-child"))->toBe([
'border-start-start-radius' => 'var(--md-button-group-outer)',
'border-end-start-radius' => 'var(--md-button-group-outer)',
])
// Half the height, never 9999px, which would scale the small inner corners away.
->and($css->declarations("[data-md-button-group][data-md-size='xl']"))->toMatchArray(['--md-button-group-full' => '68px', '--md-button-group-inner' => 'var(--md-sys-shape-corner-lg-increased)']);
});
it('squares a group', 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.
$css = ComponentStylesheet::read('button-group');
expect((string) $this->blade('
'))
->toContain('data-md-shape="square"')
->and((string) $this->blade('
'))
->toContain('data-md-button-group="connected"')
->toContain('data-md-shape="square"')
->and((string) $this->blade('
'))
->toContain('data-md-shape="round"')
->and($css->declarations("[data-md-button-group='standard'][data-md-shape='square'] > *"))->toBe(['border-radius' => 'var(--md-button-group-square)'])
->and($css->declarations("[data-md-button-group='standard'][data-md-shape='square'] > [aria-pressed='true']"))->toBe(['border-radius' => 'var(--md-button-group-full)']);
});
it('widens a pressed label button while its neighbours give way', function () {
$css = ComponentStylesheet::read('button-group');
expect($css->declarations("[data-md-button-group='standard'] > :not([data-md-icon-button]):active:not(:disabled, [aria-disabled='true'])"))
->toBe(['padding-inline' => 'calc(var(--md-button-group-pad) + var(--md-button-group-grow))'])
->and($css->declarations("[data-md-button-group='standard'] > :not([data-md-icon-button]):active:not(:disabled, [aria-disabled='true']) + :not([data-md-icon-button])"))
->toBe(['padding-inline-start' => 'calc(var(--md-button-group-pad) - var(--md-button-group-grow))'])
->and($css->declarations("[data-md-button-group][data-md-size='lg']"))->toMatchArray(['--md-button-group-pad' => 'var(--md-sys-measurement-space600)', '--md-button-group-grow' => '16px']);
});
it('takes over aria-pressed for a group that carries a selection', function () {
$html = (string) $this->blade('
');
expect($html)
->toContain('data-md-selection="single"')
->toContain('data-md-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-md-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-md-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(ComponentStylesheet::read('button-group')->declarations('[data-md-button-group]'))->toMatchArray(['display' => 'inline-flex'])
->not->toHaveKey('flex-wrap');
});
it('marks icon buttons, which keep their width when a group is pressed', function () {
expect((string) $this->blade('
'))->toContain('data-md-icon-button')
->and((string) $this->blade('
'))->not->toContain('data-md-icon-button');
});