]*\bclass="([^"]*)"/', (string) test()->blade($blade), $matches); return $matches[1] ?? ''; } it('is a text button in the primary colour by default', function () { expect(buttonClasses('')) ->toContain('text-primary') ->not->toContain('bg-primary'); }); it('draws each M3 variant', function (string $variant, string $classes) { expect(buttonClasses(""))->toContain($classes); })->with([ 'filled' => ['filled', 'bg-primary text-on-primary'], 'tonal' => ['tonal', 'bg-secondary-container text-on-secondary-container'], 'outlined' => ['outlined', 'border-outline-variant text-on-surface-variant'], 'elevated' => ['elevated', 'bg-surface-container-low text-primary'], 'text' => ['text', 'text-primary'], ]); it('draws a variant in another colour role', function () { expect(buttonClasses(''))->toContain('bg-tertiary text-on-tertiary') ->and(buttonClasses(''))->toContain('bg-error-container text-on-error-container') ->and(buttonClasses(''))->toContain('text-success'); }); it('keeps the maryUI and ReStride shorthands', function () { expect(buttonClasses(''))->toContain('bg-primary text-on-primary') ->and(buttonClasses(''))->toContain('bg-error text-on-error') ->and(buttonClasses(''))->toContain('bg-warning text-on-warning'); }); it('falls back to the defaults for values it does not know', function () { expect(buttonClasses('')) ->toContain('text-primary') ->toContain('h-10 gap-2 px-4 type-label-lg'); }); it('sizes a label button on Expressive\'s scale', function (string $size, string $classes, string $icon) { $html = (string) $this->blade(""); expect($html)->toContain($classes)->toContain($icon); })->with([ 'xs' => ['xs', 'h-8 gap-2 px-3 type-label-lg', 'size-5'], 'sm' => ['sm', 'h-10 gap-2 px-4 type-label-lg', 'size-5'], 'md' => ['md', 'h-14 gap-2 px-6 type-title-md', 'size-6'], 'lg' => ['lg', 'h-24 gap-3 px-12 type-headline-sm', 'size-8'], 'xl' => ['xl', 'h-34 gap-4 px-16 type-headline-lg', 'size-10'], ]); it('rounds by default, squares on request, and squares off further while pressed', function () { expect(buttonClasses(''))->toContain('rounded-corner-full')->toContain('active:rounded-corner-md') ->and(buttonClasses(''))->toContain('rounded-corner-lg') ->and(buttonClasses(''))->toContain('rounded-corner-xl')->toContain('active:rounded-corner-lg'); }); it('reaches a 48px touch target below the medium size', function () { expect(buttonClasses(''))->toContain('after:min-h-12') ->and(buttonClasses(''))->not->toContain('after:min-h-12'); }); it('is an icon button named by its tooltip when it has no label', function () { $html = (string) $this->blade(''); expect($html) ->toContain('aria-label="Close menu"') ->toContain('size-10') ->toContain('text-on-surface-variant') ->toContain('popover="manual"') ->toContain('aria-hidden="true"'); }); it('sizes an icon button by width', function () { expect(buttonClasses(''))->toContain('h-10 w-13') ->and(buttonClasses(''))->toContain('h-34 w-26'); }); it('toggles with aria-pressed and M3\'s selected colours and shape', function () { expect((string) $this->blade(''))->toContain('aria-pressed="true"') ->and(buttonClasses(''))->toContain('bg-primary')->toContain('rounded-corner-md') ->and(buttonClasses(''))->toContain('bg-surface-container text-on-surface-variant')->toContain('rounded-corner-full') ->and(buttonClasses(''))->toContain('bg-secondary text-on-secondary') ->and(buttonClasses(''))->toContain('bg-inverse-surface text-inverse-on-surface'); }); it('squares a selected round icon button and rounds a selected square one', function () { expect(buttonClasses(''))->toContain('rounded-corner-md') ->and(buttonClasses(''))->toContain('rounded-corner-full') ->and((string) $this->blade(''))->toContain('text-primary'); }); it('navigates inside the app, and leaves it for an external link', function () { $this->blade('') ->assertSee('assertSee('href="/admin/shares"', false) ->assertSee('wire:navigate', false) ->assertDontSee('type="button"', false); $this->blade('') ->assertSee('target="_blank"', false) ->assertSee('rel="noopener"', false) ->assertDontSee('wire:navigate', false); }); it('disables a button, and a link as far as a link can be', function () { $this->blade('') ->assertSee('disabled="disabled"', false) ->assertSee('disabled:bg-on-surface/10', false); $this->blade('') ->assertSee('aria-disabled="true"', false) ->assertSee('tabindex="-1"', false); }); it('shows the loading indicator while its own action runs', function () { $this->blade('') ->assertSee('wire:loading.attr="disabled"', false) ->assertSee('wire:target="save"', false); $this->blade('') ->assertSee('wire:target="upload"', false); }); it('hides a responsive label below expanded', function () { $this->blade('') ->assertSee('New share', false); }); it('anchors its tooltip to itself', function () { $html = (string) $this->blade(''); preg_match('/anchor-name: (--material-button-[a-z0-9]+)/', $html, $anchor); expect($anchor)->not->toBeEmpty() ->and($html)->toContain("position-anchor: {$anchor[1]}") ->toContain('[position-area:bottom]') ->not->toContain('aria-label="Saves the draft"'); }); it('is a FAB on a compact window and a filled button from medium, in one element', function () { $html = (string) $this->blade(''); expect(substr_count($html, 'toBe(1) ->and($html)->toContain('max-medium:fixed')->toContain('max-medium:bg-primary-container')->toContain('bg-primary text-on-primary'); }); it('submits a form when asked', function () { $this->blade('') ->assertSee('type="submit"', false); }); it('keeps aria-pressed off a selected link, which is not a toggle', function () { expect((string) $this->blade('')) ->not->toContain('aria-pressed') ->and((string) $this->blade('')) ->toContain('aria-pressed="true"'); });