blade('')) ->toContain('role="progressbar"') ->toContain('aria-label="Progress"') ->toContain('aria-valuemin="0"') ->toContain('aria-valuemax="100"') ->toContain('aria-valuenow="42"') ->toContain('data-value="42"') ->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-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-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, string $ink, string $track) { expect((string) $this->blade('', ['color' => $color])) ->toContain($ink) ->toContain($track); })->with([ 'primary' => ['primary', 'text-primary', 'stroke-secondary-container'], 'secondary' => ['secondary', 'text-secondary', 'stroke-secondary-container'], 'tertiary' => ['tertiary', 'text-tertiary', 'stroke-tertiary-container'], 'error' => ['error', 'text-error', 'stroke-error-container'], 'unknown, as primary' => ['purple', 'text-primary', 'stroke-secondary-container'], ]); it('sizes each shape as M3 does', function (string $template, string $size, array $flags) { $html = (string) $this->blade($template); expect($html)->toContain($size); foreach (['data-circular', 'data-wavy', 'data-thick'] as $flag) { in_array($flag, $flags, true) ? expect($html)->toContain($flag) : expect($html)->not->toContain($flag); } })->with([ 'linear' => ['', 'w-full h-1 ', []], 'linear thick' => ['', 'h-2 ', ['data-thick']], 'linear wavy' => ['', 'h-2.5 ', ['data-wavy']], 'linear wavy thick' => ['', 'h-3.5 ', ['data-wavy', 'data-thick']], 'circular' => ['', 'size-10 ', ['data-circular']], 'circular thick' => ['', 'size-11 ', ['data-circular', 'data-thick']], 'circular wavy' => ['', 'size-12 ', ['data-circular', 'data-wavy']], 'circular wavy thick' => ['', 'size-13 ', ['data-circular', 'data-wavy', 'data-thick']], ]); it('takes the caller\'s size instead of its own', function () { expect((string) $this->blade('')) ->toContain('size-24') ->not->toContain('size-12'); expect((string) $this->blade('')) ->toContain('w-64') ->not->toContain('w-full'); }); it('follows a value bound in Alpine', function () { $html = (string) $this->blade(''); expect($html) ->toContain('x-bind:data-value="upload.progress"') ->toContain('x-bind:aria-valuenow="((v) => v === null') ->toContain('Math.min(Math.max(Number(v), 0), 1))(upload.progress)') ->toContain('data-max="1"') ->not->toContain(' data-value='); });