Files
livewire-material/tests/Feature/Components/ProgressTest.php
T
Andreas Reinhold / reiniandClaude Sonnet 5 6897306e18 Prove no Tailwind utility class remains in resources/ or tests/
StylesheetsTest.php's new test runs DesignGuard's own family table (check
(i)) over resources/views, resources/js, src, tests/Browser and
tests/Feature — the guard turned on the package's own source, so the two
never drift apart. tests/Feature/DesignGuardTest.php and
tests/Fixtures/design-guard/ carry Tailwind on purpose and are left out.

The scan found Tailwind-shaped class strings used as caller classes in 21
test files (a component test proving a caller's class lands on the root or
a caller's size wins, mostly), left over from before Tailwind cleared the
whole stack: replaced with neutral application-style names (`app-hero-icon`)
or, where one already carries the right meaning, an `md-*` class
(`md-text-end`, `md-ink-warning`). BreakpointsTest.php's own heredoc probe —
which needs a genuine Tailwind-shaped breakpoint prefix to prove its
detection works — now builds that one string from a placeholder at runtime,
so its own source stays clean for this same scan.

Plan step 42 (Phase F), Part A.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
2026-09-15 09:32:36 +02:00

130 lines
6.8 KiB
PHP

<?php
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
it('is a named, determinate progressbar with its value', function () {
expect((string) $this->blade('<x-progress value="42" />'))
->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('<x-progress label="Uploading" />'))
->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('<x-progress value="10" :label="false" />'))
->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' => ['<x-progress :value="-5" />', '0', '100'],
'above the maximum' => ['<x-progress :value="250" />', '100', '100'],
'a custom maximum' => ['<x-progress :value="3.5" :max="5" />', '3.5', '5'],
'a maximum that cannot be one' => ['<x-progress :value="30" :max="0" />', '30', '100'],
]);
it('draws the first frame on the server, at the value', function () {
expect((string) $this->blade('<x-progress value="42" />'))
->toContain('<svg x="42%" overflow="visible">')
->toContain('x2="42%"')
->toContain('<circle cx="100%"')
->toContain('wire:ignore');
expect((string) $this->blade('<x-progress value="25" circular />'))
->toContain('viewBox="0 0 40 40"')
->toContain('<path d="M20 2A18 18 0 0 1 38 20" stroke="currentColor" />')
->toContain('<path d="M36.579 27.01A18 18 0 1 1 12.99 3.421" />');
});
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('<x-progress value="50" :color="$color" />', ['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('<x-progress value="50" color="purple" />'))->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' => ['<x-progress />', [], '[data-md-progress]:not([data-md-circular])', ['height' => '4px']],
'linear thick' => ['<x-progress thick />', ['data-md-thick'], '[data-md-progress]:not([data-md-circular])[data-md-thick]', ['height' => '8px']],
'linear wavy' => ['<x-progress wavy />', ['data-md-wavy'], '[data-md-progress]:not([data-md-circular])[data-md-wavy]', ['height' => '10px']],
'linear wavy thick' => ['<x-progress wavy thick />', ['data-md-wavy', 'data-md-thick'], '[data-md-progress]:not([data-md-circular])[data-md-wavy][data-md-thick]', ['height' => '14px']],
'circular' => ['<x-progress circular />', ['data-md-circular'], '[data-md-progress][data-md-circular]', ['width' => '40px', 'height' => '40px']],
'circular thick' => ['<x-progress circular thick />', ['data-md-circular', 'data-md-thick'], '[data-md-progress][data-md-circular][data-md-thick]', ['width' => '44px', 'height' => '44px']],
'circular wavy' => ['<x-progress circular wavy />', ['data-md-circular', 'data-md-wavy'], '[data-md-progress][data-md-circular][data-md-wavy]', ['width' => '48px', 'height' => '48px']],
'circular wavy thick' => ['<x-progress 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('<x-progress circular wavy class="app-upload-progress" />'))
->toContain('class="app-upload-progress"')
->toContain('data-md-circular')
->toContain('data-md-wavy');
expect((string) $this->blade('<x-progress class="app-progress-bar" />'))
->toContain('class="app-progress-bar"')
->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]):is([dir='rtl'], [dir='rtl'] *)"))->toBe(['rotate' => '180deg'])
->and($css->has("[data-md-progress][data-md-circular]:is([dir='rtl'], [dir='rtl'] *)"))->toBeFalse();
});
it('follows a value bound in Alpine', function () {
$html = (string) $this->blade('<x-progress bind="upload.progress" max="1" />');
expect($html)
->toContain('x-bind:data-md-value="upload.progress"')
->toContain('x-bind:aria-valuenow="((v) =&gt; v === null')
->toContain('Math.min(Math.max(Number(v), 0), 1))(upload.progress)')
->toContain('data-md-max="1"')
->not->toContain(' data-md-value=');
});