Merge branch 'worktree-agent-a3082a7e6ef1bee94'

This commit is contained in:
Andreas Reinhold / reini
2026-09-14 10:38:57 +02:00
18 changed files with 721 additions and 63 deletions
+15
View File
@@ -60,3 +60,18 @@ it('makes a directly actionable card the one tab stop, named by its title', func
->not->toContain('data-list-actionable')
->not->toContain('tabindex');
});
it('draws M3\'s dragged state while the application says the card is being carried', function () {
$css = (string) file_get_contents(__DIR__.'/../../../resources/css/components/list.css');
expect((string) $this->blade('<x-card data-dragged variant="elevated">Body</x-card>'))
->toContain('data-dragged')
->and($css)
->toContain('[data-card][data-dragged] {')
->toContain('var(--md-sys-elevation-3),')
->toContain("[data-card='elevated'][data-dragged] {")
->toContain('var(--md-sys-elevation-4),')
->toContain('calc(var(--md-sys-state-dragged-state-layer-opacity) * 100%)')
->toContain(':not([data-dragged]):hover')
->toContain(':not([data-dragged]):active');
});
+32
View File
@@ -156,3 +156,35 @@ it('lays a label over an item on a scrim, and passes attributes to the item', fu
->and((string) $this->blade('<x-carousel-item>A</x-carousel-item>'))
->not->toContain('data-material-carousel-label');
});
it('lets a multi-aspect carousel size each item by its own ratio, between 9:16 and 16:9', function () {
$html = (string) $this->blade(<<<'BLADE'
<x-carousel layout="multi-aspect" label="Clips" height="180">
<x-carousel-item aspect="16/9">A</x-carousel-item>
<x-carousel-item aspect="9:16">B</x-carousel-item>
<x-carousel-item>C</x-carousel-item>
<x-carousel-item aspect="4">D</x-carousel-item>
<x-carousel-item aspect="0.1">E</x-carousel-item>
</x-carousel>
BLADE);
expect($html)
->toContain('data-material-carousel="multi-aspect"')
->toContain('--material-carousel-pad: 16px')
->toContain('--material-carousel-height: 180px')
->toContain('ps-(--material-carousel-pad)')
->toContain('data-padding="16" data-padding-end="0"')
// M3's own range holds whatever the item asks for: 16/9 at the top, 9/16 at the bottom.
->toContain('aspect-ratio: 1.7778')
->toContain('aspect-ratio: 0.5625')
->toContain('aspect-ratio: 1')
->toContain('w-auto rounded-corner-xl')
// Uncontained scrolling, and nothing masks an item whose width is its own.
->not->toContain('snap-mandatory')
->not->toContain('--material-carousel-slot')
->not->toContain('clip-path')
->not->toContain('x-ref="probe"')
->and(substr_count($html, 'aspect-ratio: 1.7778'))->toBe(2)
->and((string) $this->blade('<x-carousel><x-carousel-item aspect="16/9">A</x-carousel-item></x-carousel>'))
->not->toContain('aspect-ratio');
});
+18
View File
@@ -6,3 +6,21 @@ it('separates horizontally or vertically, inset on request', function () {
->and((string) $this->blade('<x-divider inset />'))->toContain('ms-4')
->and((string) $this->blade('<x-divider decorative />'))->toContain('aria-hidden="true"')->not->toContain('role="separator"');
});
it('heads a group with a subheader, the rule running on from the words', function () {
$html = (string) $this->blade('<x-divider text="Recent" />');
expect($html)
->toContain('flex shrink-0 items-center gap-1 pb-2')
->toContain('<span class="shrink-0 type-title-sm text-on-surface-variant">Recent</span>')
->toMatch('/role="separator" aria-orientation="horizontal"\s+class="me-2 h-px min-w-0 flex-1 bg-outline-variant"/')
->and(strpos($html, 'Recent'))->toBeLessThan(strpos($html, 'role="separator"'))
->and((string) $this->blade('<x-divider text="Recent" inset />'))->toContain('flex shrink-0 items-center gap-1 pb-2 ms-4')
->and((string) $this->blade('<x-divider text="Recent" decorative />'))
->toContain('>Recent</span>')
->toContain('aria-hidden="true"')
->not->toContain('role="separator"')
->and((string) $this->blade('<x-divider text="Recent" vertical />'))
->not->toContain('Recent')
->toContain('aria-orientation="vertical"');
});
+17
View File
@@ -93,3 +93,20 @@ it('marks a selected item and takes controls in its slots', function () {
->toContain('<input type="checkbox">')
->toContain('<button>⋮</button>');
});
it('leads with M3\'s video, in the size the item\'s height calls for', function () {
expect((string) $this->blade('<x-list-item title="Sunrise" video="/poster.jpg" />'))
->toContain('h-14 w-25')
->toContain('rounded-corner-sm')
->toContain('<img src="/poster.jpg" alt="" />')
->toContain('min-h-18 py-2')
->and((string) $this->blade('<x-list-item title="Sunrise" overline="Draft" description="7:48" video="/poster.jpg" />'))
->toContain('h-16 w-28.5')
->toContain('min-h-22 py-3')
->and((string) $this->blade('<x-list-item title="Sunrise"><x-slot:video><video src="/clip.mp4"></video></x-slot:video></x-list-item>'))
->toContain('<video src="/clip.mp4"></video>')
->toContain('h-14 w-25')
->and((string) $this->blade('<x-list-item title="Sunrise" />'))
->not->toContain('h-14 w-25')
->toContain('min-h-14 py-2');
});
+52 -2
View File
@@ -127,7 +127,7 @@ it('slides a side sheet in from either edge, and is a pane from expanded when as
it('draws a modal bottom sheet with a drag handle, or a standard one without a scrim', function () {
expect((string) $this->blade('<x-bottom-sheet title="Share via">Body</x-bottom-sheet>'))
->toContain('...materialBottomSheet(false)')
->toContain('...materialBottomSheet(false, JSON.parse(')
->toContain('bg-scrim/32')
->toContain('x-trap.inert.noscroll="open"')
->toContain('rounded-t-corner-xl bg-surface-container-low')
@@ -137,9 +137,59 @@ it('draws a modal bottom sheet with a drag handle, or a standard one without a s
->toContain('touch-target h-1 w-8 rounded-corner-full bg-on-surface-variant ')
->toContain('--sheet-max-height: min(50dvh, calc(100dvh - 72px))')
->and((string) $this->blade('<x-bottom-sheet standard>Body</x-bottom-sheet>'))
->toContain('...materialBottomSheet(true)')
->toContain('...materialBottomSheet(true, JSON.parse(')
->not->toContain('bg-scrim/32')
->not->toContain('aria-modal')
->and((string) $this->blade('<x-bottom-sheet height="90dvh">Body</x-bottom-sheet>'))
->toContain('--sheet-max-height: min(90dvh, calc(100dvh - 72px))');
});
it('is M3\'s standard side sheet from expanded, and the modal one below', function () {
$html = (string) $this->blade('<x-drawer standard title="Filters">Body</x-drawer>');
expect($html)
->toContain('data-standard')
->toContain('expanded:sticky expanded:top-0 expanded:h-dvh expanded:shrink-0 expanded:self-start')
->toContain('expanded:relative expanded:top-0 expanded:z-auto expanded:h-full expanded:rounded-corner-none expanded:bg-surface expanded:shadow-none')
->toContain('expanded:border-s expanded:border-outline-variant')
->toContain('--sheet-width: min(25rem, 25rem)')
->toContain("matchMedia('(width >= 52.5rem)')")
->toContain('x-trap.inert.noscroll="open && ! wide"')
->toContain('bg-scrim/32 expanded:hidden')
->toContain('aria-label="Close"')
->and((string) $this->blade('<x-drawer standard side="start" width="30rem">Body</x-drawer>'))
->toContain('expanded:border-e expanded:border-outline-variant')
->toContain('--sheet-width: min(30rem, 25rem)')
->and((string) $this->blade('<x-drawer standard pane>Body</x-drawer>'))
->toContain('data-pane')
->not->toContain('data-standard')
->and((string) $this->blade('<x-drawer title="Filters">Body</x-drawer>'))
->not->toContain('data-standard')
->not->toContain('expanded:hidden');
});
it('gives a bottom sheet M3\'s preset heights, cycled from the drag handle', function () {
$html = (string) $this->blade('<x-bottom-sheet title="Share via" snap>Body</x-bottom-sheet>');
expect($html)
->toContain('--sheet-stop: 50dvh; --sheet-max-height: min(var(--sheet-stop), calc(100dvh - 72px))')
->toContain('h-(--sheet-max-height) transition-[height]')
->toContain('x-ref="probe"')
->toContain('aria-label="Change the sheet height"')
->toContain('x-bind:aria-label="handleLabel"')
->toContain('x-on:click="activate()"')
->toContain('<span class="sr-only" aria-live="polite" x-text="announcement"></span>')
->toContain('25dvh')
->toContain('90dvh')
->toContain('Height 2 of 3')
->and((string) $this->blade('<x-bottom-sheet heights="30dvh,60dvh" height="60dvh">Body</x-bottom-sheet>'))
->toContain('--sheet-stop: 60dvh;')
->toContain('30dvh')
->and((string) $this->blade('<x-bottom-sheet :heights="[25, 50, 90]">Body</x-bottom-sheet>'))
->toContain('--sheet-stop: 50dvh;')
->and((string) $this->blade('<x-bottom-sheet heights="50dvh">Body</x-bottom-sheet>'))
->toContain('--sheet-max-height: min(50dvh, calc(100dvh - 72px))')
->not->toContain('--sheet-stop')
->not->toContain('sr-only')
->toContain('aria-label="Close"');
});