blade('')) ->toContain('data-md-progress') ->toContain('role="progressbar"') ->toContain('aria-label="Progress"') ->toContain('aria-valuemin="0"') ->toContain('aria-valuemax="100"') ->toContain('aria-valuenow="42"') ->toContain('data-md-value="42"') ->toContain('data-md-color="primary"') ->toContain('x-data="materialProgress"'); }); it('leaves out aria-valuenow while indeterminate', function () { expect((string) $this->blade('')) ->toContain('role="progressbar"') ->toContain('aria-label="Uploading"') ->not->toContain('aria-valuenow') ->not->toContain('data-md-value'); }); it('says nothing where something else already does', function () { expect((string) $this->blade('')) ->toContain('aria-hidden="true"') ->not->toContain('role="progressbar"') ->not->toContain('aria-label') ->not->toContain('aria-valuenow'); }); it('clamps the value into its range', function (string $template, string $now, string $max) { expect((string) $this->blade($template)) ->toContain("aria-valuenow=\"{$now}\"") ->toContain("aria-valuemax=\"{$max}\"") ->toContain("data-md-value=\"{$now}\""); })->with([ 'below zero' => ['', '0', '100'], 'above the maximum' => ['', '100', '100'], 'a custom maximum' => ['', '3.5', '5'], 'a maximum that cannot be one' => ['', '30', '100'], ]); it('draws the first frame on the server, at the value', function () { expect((string) $this->blade('')) ->toContain('') ->toContain('x2="42%"') ->toContain('toContain('wire:ignore'); expect((string) $this->blade('')) ->toContain('viewBox="0 0 40 40"') ->toContain('') ->toContain(''); }); it('draws the active indicator in the colour and the track in its container', function (string $color, array $declarations) { $css = ComponentStylesheet::read('progress'); expect((string) $this->blade('', ['color' => $color])) ->toContain("data-md-color=\"{$color}\"") ->and($css->declarations("[data-md-progress][data-md-color='{$color}']"))->toBe($declarations); })->with([ 'secondary' => ['secondary', ['color' => 'var(--md-sys-color-secondary)', 'stroke' => 'var(--md-sys-color-secondary-container)']], 'tertiary' => ['tertiary', ['color' => 'var(--md-sys-color-tertiary)', 'stroke' => 'var(--md-sys-color-tertiary-container)']], 'error' => ['error', ['color' => 'var(--md-sys-color-error)', 'stroke' => 'var(--md-sys-color-error-container)']], ]); it('falls back to primary for an unknown colour, secondary-container track included', function () { $css = ComponentStylesheet::read('progress'); expect((string) $this->blade(''))->toContain('data-md-color="primary"') ->and($css->declarations('[data-md-progress]'))->toMatchArray([ 'color' => 'var(--md-sys-color-primary)', 'stroke' => 'var(--md-sys-color-secondary-container)', ]); }); it('sizes each shape as M3 does', function (string $template, array $flags, string $selector, array $declarations) { $html = (string) $this->blade($template); foreach (['data-md-circular', 'data-md-wavy', 'data-md-thick'] as $flag) { in_array($flag, $flags, true) ? expect($html)->toContain($flag) : expect($html)->not->toContain($flag); } expect(ComponentStylesheet::read('progress')->declarations($selector))->toMatchArray($declarations); })->with([ 'linear' => ['', [], '[data-md-progress]:not([data-md-circular])', ['height' => '4px']], 'linear thick' => ['', ['data-md-thick'], '[data-md-progress]:not([data-md-circular])[data-md-thick]', ['height' => '8px']], 'linear wavy' => ['', ['data-md-wavy'], '[data-md-progress]:not([data-md-circular])[data-md-wavy]', ['height' => '10px']], 'linear wavy thick' => ['', ['data-md-wavy', 'data-md-thick'], '[data-md-progress]:not([data-md-circular])[data-md-wavy][data-md-thick]', ['height' => '14px']], 'circular' => ['', ['data-md-circular'], '[data-md-progress][data-md-circular]', ['width' => '40px', 'height' => '40px']], 'circular thick' => ['', ['data-md-circular', 'data-md-thick'], '[data-md-progress][data-md-circular][data-md-thick]', ['width' => '44px', 'height' => '44px']], 'circular wavy' => ['', ['data-md-circular', 'data-md-wavy'], '[data-md-progress][data-md-circular][data-md-wavy]', ['width' => '48px', 'height' => '48px']], 'circular wavy thick' => ['', ['data-md-circular', 'data-md-wavy', 'data-md-thick'], '[data-md-progress][data-md-circular][data-md-wavy][data-md-thick]', ['width' => '52px', 'height' => '52px']], ]); it('takes the caller\'s size instead of its own, its class landing on the root untouched', function () { expect((string) $this->blade('')) ->toContain('class="size-24"') ->toContain('data-md-circular') ->toContain('data-md-wavy'); expect((string) $this->blade('')) ->toContain('class="w-64"') ->not->toContain('data-md-circular'); }); it('mirrors the linear indicator in a right-to-left document, unconditionally', function () { $css = ComponentStylesheet::read('progress'); expect($css->declarations('[data-md-progress]:not([data-md-circular]):dir(rtl)'))->toBe(['rotate' => '180deg']) ->and($css->has('[data-md-progress][data-md-circular]:dir(rtl)'))->toBeFalse(); }); it('follows a value bound in Alpine', function () { $html = (string) $this->blade(''); expect($html) ->toContain('x-bind:data-md-value="upload.progress"') ->toContain('x-bind:aria-valuenow="((v) => v === null') ->toContain('Math.min(Math.max(Number(v), 0), 1))(upload.progress)') ->toContain('data-md-max="1"') ->not->toContain(' data-md-value='); });