diff --git a/tests/Browser/CarouselTest.php b/tests/Browser/CarouselTest.php index ddf8022d..9b45d8f1 100644 --- a/tests/Browser/CarouselTest.php +++ b/tests/Browser/CarouselTest.php @@ -34,11 +34,16 @@ class CarouselMorphProbe extends Component /** * A script run against one carousel of the showcase (0 multi-browse, 1 hero, 2 uncontained, - * 3 full-screen), or of another page under `scope`, scrolled into view. `root`, `scroller` and - * `items` are in scope, with `surface(i)`, `inset(i)` (the mask on each side of item i), - * `snap(i)` (the scroll offset that brings item i into focus) and `at(i)`; the script's value is - * the result. Pest retries a failing assertion and gives each attempt a second: keep scripts well - * inside it. + * 3 multi-aspect, 4 full-screen), or of another page under `scope`, scrolled into view. `root`, + * `scroller` and `items` are in scope, with `surface(i)`, `inset(i)` (the mask on each side of + * item i), `open(i)` (item i is at the largest size any item has here), `snap(i)` (the scroll + * offset that brings item i into focus) and `at(i)`; the script's value is the result. Pest + * retries a failing assertion and gives each attempt a second: keep scripts well inside it. + * + * `open(i)` rather than a zero inset: M3's 16dp of content padding, the carousel's default since + * containment.md C-12, shrinks every keyline near the ends (Compose's + * createShiftedKeylineListForContentPadding), so a large item resting at the start or the end is + * masked by that share of the padding — 1.33px on each side in the showcase's first carousel. */ function onCarousel(int $index, string $body, string $scope = '#carousel'): string { @@ -51,6 +56,7 @@ function onCarousel(int $index, string $body, string $scope = '#carousel'): stri const size = parseFloat(root.style.getPropertyValue('--material-carousel-slot')) const surface = (i) => items[i].querySelector('[data-material-carousel-surface]') const inset = (i) => parseFloat(surface(i).style.getPropertyValue('--material-carousel-inset')) + const open = (i) => inset(i) - Math.min(...items.map((_, j) => inset(j))) < 0.5 const snap = (i) => Math.min(Math.max(i * (size + gap) - parseFloat(items[i].style.scrollMarginInlineStart), 0), scroller.scrollWidth - scroller.clientWidth) const at = (i) => Math.abs(Math.abs(scroller.scrollLeft) - snap(i)) < 1.5 const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) @@ -73,7 +79,7 @@ it('masks items by their place between the keylines, and changes the large one a $page = carouselShowcase() ->assertNoJavaScriptErrors() ->assertScript(onCarousel(0, <<<'JS' - return size > 0 && inset(0) < 0.5 && inset(items.length - 1) > 0.5 + return size > 0 && open(0) && ! open(items.length - 1) && getComputedStyle(surface(0)).clipPath.startsWith('inset(') JS)); @@ -122,7 +128,7 @@ it('moves one item with the arrow keys, and to the ends with Home and End', func ->assertScript(onCarousel(0, 'return at(1)')); $page->keys(':focus', 'End') - ->assertScript(onCarousel(0, 'return Math.abs(scroller.scrollLeft - (scroller.scrollWidth - scroller.clientWidth)) < 1.5 && inset(items.length - 1) < 0.5 && root.querySelector(\'[aria-label="Next"]\').disabled')); + ->assertScript(onCarousel(0, 'return Math.abs(scroller.scrollLeft - (scroller.scrollWidth - scroller.clientWidth)) < 1.5 && open(items.length - 1) && root.querySelector(\'[aria-label="Next"]\').disabled')); $page->keys(':focus', 'Home') ->assertScript(onCarousel(0, 'return at(0)')); @@ -141,11 +147,11 @@ it('snaps a scroll that stops between items onto an item', function () { it('brings a partly hidden item into focus when it is pressed', function () { $page = carouselShowcase() - ->assertScript(onCarousel(0, 'return inset(4) > 0.5')); + ->assertScript(onCarousel(0, 'return ! open(4)')); $page->script(onCarousel(0, 'items[4].querySelector(\'[data-material-carousel-content]\').click()')); - $page->assertScript(onCarousel(0, 'return inset(4) < 0.5 && scroller.scrollLeft > 0')); + $page->assertScript(onCarousel(0, 'return open(4) && scroller.scrollLeft > 0')); }); it('scrolls instantly and leaves every item unmasked under reduced motion', function () { @@ -183,11 +189,11 @@ it('mirrors in a right-to-left page', function () { $page = visit('/carousel-rtl-probe')->waitForEvent('networkidle') ->assertNoJavaScriptErrors() - ->assertScript(onCarousel(0, 'return size > 0 && at(0) && inset(0) < 0.5 && inset(items.length - 1) > 0.5', 'body')); + ->assertScript(onCarousel(0, 'return size > 0 && at(0) && open(0) && ! open(items.length - 1)', 'body')); $page->keys('[data-material-carousel-item] >> nth=0', 'ArrowLeft') ->assertScript(onCarousel(0, <<<'JS' - return scroller.scrollLeft < -1 && at(1) && inset(0) > 0.5 && inset(1) < 0.5 + return scroller.scrollLeft < -1 && at(1) && ! open(0) && open(1) && parseFloat(surface(0).style.getPropertyValue('--material-carousel-shift')) < 0 JS, 'body'));