]*>/s', (string) test()->blade($blade), $matches); return $matches[0] ?? ''; } it('is an outlined assist chip, a button, by default', function () { $html = (string) $this->blade(''); expect(chipTag('', 'button')) ->toContain('data-chip="assist"') ->toContain('type="button"') ->toContain('h-8') ->toContain('rounded-corner-sm') ->toContain('type-label-lg') ->toContain('border-outline-variant text-on-surface') ->toContain('ps-1.75 pe-3.75') ->and($html) ->toContain('me-2 size-4.5 text-primary') ->toContain('ms-2 size-4.5 text-primary') ->toContain('Add to calendar'); }); it('pads a chip without icons by 16px on both sides', function () { expect(chipTag('', 'button'))->toContain('ps-3.75 pe-3.75'); }); it('falls back to an assist chip for a type it does not know', function () { expect(chipTag('', 'button'))->toContain('data-chip="assist"'); }); it('draws an elevated chip on surface-container-low at elevation 1', function () { expect(chipTag('', 'button')) ->toContain('border-transparent bg-surface-container-low text-on-surface shadow-elevation-1 hover:shadow-elevation-2') ->not->toContain('border-outline-variant'); }); it('greys a disabled chip, flat or elevated', function () { expect(chipTag('', 'button')) ->toContain('disabled') ->toContain('border-on-surface/12 text-on-surface/38') ->and(chipTag('', 'button')) ->toContain('bg-on-surface/12 text-on-surface/38') ->and((string) $this->blade('')) ->not->toContain('text-primary'); }); it('links with wire:navigate, and a disabled link leaves the tab order', function () { expect(chipTag('', 'a')) ->toContain('href="/directions"') ->toContain('wire:navigate') ->not->toContain('type=') ->and(chipTag('', 'a')) ->toContain('target="_blank"') ->toContain('rel="noopener"') ->not->toContain('wire:navigate') ->and(chipTag('', 'a')) ->toContain('aria-disabled="true"') ->toContain('tabindex="-1"') ->toContain('pointer-events-none'); }); it('draws a suggestion chip in on-surface-variant', function () { expect(chipTag('', 'button')) ->toContain('data-chip="suggestion"') ->toContain('border-outline-variant text-on-surface-variant') ->and(chipTag('', 'button')) ->toContain('bg-surface-container-low text-on-surface-variant shadow-elevation-1'); }); it('attaches a plain tooltip', function () { $html = (string) $this->blade(''); expect($html) ->toContain('popover="manual"') ->toContain('Share this file') ->toMatch('/]*style="anchor-name: --material-chip-[a-z0-9]{10}"/'); }); it('makes a filter chip without a model a toggle button the caller owns', function () { $html = (string) $this->blade(''); expect(chipTag('', 'button')) ->toContain('data-chip="filter"') ->toContain('aria-pressed="true"') ->toContain('wire:click="toggleStarred"') ->toContain('aria-pressed:bg-secondary-container aria-pressed:text-on-secondary-container') ->and(chipTag('', 'button')) ->toContain('aria-pressed="false"') ->and($html) ->not->toContain('type="checkbox"') ->toContain('group-aria-pressed/chip:w-4.5') ->toContain('data-chip-check'); }); it('makes a bound filter chip a native checkbox under the chip', function () { $html = (string) $this->blade(''); expect(chipTag('', 'label')) ->toContain('data-chip="filter"') ->toContain('me-4') ->toContain('wire:key="photos"') ->toContain('has-checked:border-transparent has-checked:bg-secondary-container has-checked:text-on-secondary-container') ->toContain('has-focus-visible:outline-3') ->not->toContain('x-model') ->and(chipTag('', 'input')) ->toContain('type="checkbox"') ->toContain('value="photos"') ->toContain('x-model="kinds"') ->toContain('sr-only') ->not->toContain('me-4') ->not->toContain('checked') ->and($html) ->toContain('group-has-checked/chip:w-4.5'); }); it('submits a named filter chip, checked when selected, and greys a disabled one', function () { expect(chipTag('', 'input')) ->toContain('name="starred"') ->toContain('checked') ->and(chipTag('', 'input')) ->toContain('disabled') ->and(chipTag('', 'label')) ->toContain('border-on-surface/12 text-on-surface/38 has-checked:border-transparent has-checked:bg-on-surface/12') ->toContain('cursor-not-allowed'); }); it('swaps a filter chip\'s icon for the check when selected', function () { $html = (string) $this->blade(''); expect($html) ->toContain('group-has-checked/chip:opacity-0') ->toContain('text-primary') ->toContain('bg-surface-container-low text-on-surface-variant shadow-elevation-1') ->not->toContain('w-0') ->and(substr_count($html, 'toBe(2); }); it('draws an input chip with an avatar, an icon or neither', function () { expect(chipTag('', 'span')) ->toContain('data-chip="input"') ->toContain('border-outline-variant text-on-surface-variant') ->not->toContain('x-data') ->and((string) $this->blade('')) ->toContain('ps-0.75 pe-2.75') ->toContain('size-6 shrink-0 place-items-center rounded-corner-full bg-primary-container') ->toContain('>AM') ->not->toContain('and((string) $this->blade('')) ->toContain('and((string) $this->blade('')) ->toContain('ps-1.75 pe-2.75'); }); it('makes an input chip a button when it has its own action, and selects it', function () { $html = (string) $this->blade(''); expect(chipTag('', 'button')) ->toContain('data-chip-action') ->toContain('aria-pressed="true"') ->toContain('wire:click="open(1)"') ->and(chipTag('', 'span')) ->toContain('border-transparent bg-secondary-container text-on-secondary-container') ->and($html) ->toContain('me-2 size-4.5 text-primary'); }); it('gives a removable input chip a named remove button that calls wire:remove', function () { $html = (string) $this->blade(''); expect(chipTag('', 'span')) ->toContain('x-data="materialChip"') ->toContain('x-on:keydown="removeOnKey($event)"') ->not->toContain('wire:remove') ->and($html) ->toContain('data-chip-remove aria-label="Remove anna@example.com"') ->toContain('wire:click="removeRecipient(3)"') ->toContain('x-on:click="removeChip($event)"') ->toContain('') ->toContain('ps-2.75 pe-2'); }); it('runs an Alpine remove expression, or takes the chip off the page with neither', function () { expect((string) $this->blade('')) ->toContain('x-on:click="removeChip($event); tags.splice(0, 1)"') ->not->toContain('wire:click') ->and((string) $this->blade('')) ->toContain('x-on:click="removeChip($event); $root.remove()"'); }); it('leaves a client-rendered chip\'s remove button to be named in the browser', function () { expect((string) $this->blade('')) ->toContain('data-chip-remove="Remove :label"') ->not->toContain('aria-label='); }); it('disables a removable input chip and its remove button', function () { $html = (string) $this->blade(''); expect($html) ->toContain('border-on-surface/12 text-on-surface/38') ->toMatch('/]*data-chip-remove[^>]*disabled/s'); }); it('groups chips in a wrapping row named by its label, with a hint', function () { $html = (string) $this->blade(''); preg_match('/aria-labelledby="([^"]+)"/', $html, $labelledBy); preg_match('/aria-describedby="([^"]+)"/', $html, $describedBy); expect($html) ->toContain('role="group"') ->toContain('data-chip-set class="flex flex-wrap gap-2"') ->toContain("id=\"{$labelledBy[1]}\" class=\"mb-2 type-label-lg text-on-surface-variant\">File types

") ->toContain("id=\"{$describedBy[1]}\" class=\"mt-1 type-body-sm text-on-surface-variant\">Show only these

") ->not->toContain('materialChipSet'); }); it('scrolls a chip set on one line with fading edges', function () { expect((string) $this->blade('')) ->toContain('aria-label="Sort"') ->toContain('x-data="materialChipSet"') ->toContain('wire:ignore.self') ->toContain('overflow-x-auto') ->toContain('data-scroll-start:[--chip-fade-start:1.5rem]') ->not->toContain('flex-wrap'); }); it('binds filter chips to a Livewire array and shows the set\'s validation message in place of its hint', function () { $component = new class extends Component { /** @var list */ public array $kinds = ['photos']; public function save(): void { $this->validate(['kinds' => 'max:1'], ['kinds.max' => 'Pick one kind.']); } public function render(): string { return <<<'BLADE'
BLADE; } }; Livewire::test($component) ->assertSeeHtml('wire:model.live="kinds"') ->assertSeeHtml('value="photos" checked') ->assertDontSeeHtml('value="documents" checked') ->assertSee('Show only these') ->set('kinds', ['photos', 'documents']) ->assertSeeHtml('value="documents" checked') ->call('save') ->assertSee('Pick one kind.') ->assertDontSee('Show only these'); });