@primitiv-ui/react 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/package.json +6 -1
- package/src/AccessibleIcon/AccessibleIcon.tsx +32 -9
- package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +5 -0
- package/src/AccessibleIcon/types.ts +15 -2
- package/src/Accordion/Accordion.tsx +25 -4
- package/src/Accordion/__tests__/Accordion.asChild.test.tsx +46 -0
- package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +15 -0
- package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +42 -0
- package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +33 -0
- package/src/Accordion/hooks/useAccordionRoot.ts +44 -7
- package/src/Accordion/hooks/useAccordionTrigger.ts +9 -1
- package/src/Accordion/types.ts +72 -21
- package/src/Alert/Alert.tsx +36 -7
- package/src/Alert/__tests__/Alert.test.tsx +5 -0
- package/src/Alert/types.ts +18 -2
- package/src/Avatar/Avatar.tsx +99 -15
- package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +10 -0
- package/src/Avatar/__tests__/Avatar.fallback.test.tsx +30 -0
- package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +35 -0
- package/src/Avatar/hooks/useAvatarImage.ts +16 -0
- package/src/Avatar/types.ts +15 -3
- package/src/Breadcrumb/Breadcrumb.tsx +136 -18
- package/src/Breadcrumb/README.md +47 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +11 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.ellipsis.test.tsx +110 -0
- package/src/Breadcrumb/types.ts +73 -7
- package/src/Button/Button.tsx +2 -0
- package/src/Button/__tests__/Button.basic-rendering.test.tsx +6 -0
- package/src/Button/types.ts +21 -0
- package/src/Carousel/Carousel.tsx +263 -15
- package/src/Carousel/README.md +749 -72
- package/src/Carousel/__tests__/Carousel.autoplay-status.test.tsx +239 -0
- package/src/Carousel/__tests__/Carousel.drag-status.test.tsx +292 -0
- package/src/Carousel/__tests__/Carousel.dynamic-slides.test.tsx +38 -0
- package/src/Carousel/__tests__/Carousel.indicators.test.tsx +101 -0
- package/src/Carousel/__tests__/Carousel.infinite.test.tsx +1453 -0
- package/src/Carousel/__tests__/Carousel.instant-scroll.test.tsx +124 -0
- package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +88 -0
- package/src/Carousel/__tests__/Carousel.loop.test.tsx +163 -0
- package/src/Carousel/__tests__/Carousel.loopEngine.test.ts +77 -0
- package/src/Carousel/__tests__/Carousel.mouse-drag.test.tsx +504 -0
- package/src/Carousel/__tests__/Carousel.multi-slide-bounds.test.tsx +91 -0
- package/src/Carousel/__tests__/Carousel.orientation.test.tsx +207 -0
- package/src/Carousel/__tests__/Carousel.overscroll.test.tsx +532 -0
- package/src/Carousel/__tests__/Carousel.page-snap-points.test.tsx +50 -0
- package/src/Carousel/__tests__/Carousel.progress-text.test.tsx +76 -0
- package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +3 -6
- package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +2 -5
- package/src/Carousel/__tests__/Carousel.scroll-margin.test.tsx +167 -0
- package/src/Carousel/__tests__/Carousel.scroll-progress.test.tsx +518 -0
- package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +32 -11
- package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +237 -24
- package/src/Carousel/__tests__/Carousel.scroll-to-index.test.tsx +115 -0
- package/src/Carousel/__tests__/Carousel.slide-snap.test.tsx +172 -0
- package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +179 -1
- package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +91 -7
- package/src/Carousel/__tests__/Carousel.snap-type.test.tsx +35 -0
- package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +30 -5
- package/src/Carousel/__tests__/Carousel.translations.test.tsx +23 -0
- package/src/Carousel/__tests__/Carousel.wheel.test.tsx +160 -0
- package/src/Carousel/hooks/index.ts +1 -0
- package/src/Carousel/hooks/useCarouselLoop.ts +869 -0
- package/src/Carousel/hooks/useCarouselRoot.ts +338 -42
- package/src/Carousel/hooks/useCarouselSlide.ts +44 -9
- package/src/Carousel/hooks/useCarouselViewport.ts +649 -46
- package/src/Carousel/loopEngine.ts +54 -0
- package/src/Carousel/types.ts +457 -35
- package/src/Checkbox/Checkbox.tsx +9 -1
- package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +14 -0
- package/src/Checkbox/hooks/useCheckboxInput.ts +4 -0
- package/src/Checkbox/types.ts +19 -0
- package/src/CheckboxCard/CheckboxCard.tsx +13 -4
- package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +7 -0
- package/src/CheckboxCard/types.ts +65 -17
- package/src/Collapsible/Collapsible.tsx +49 -3
- package/src/Collapsible/README.md +40 -0
- package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +89 -0
- package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +27 -0
- package/src/Collapsible/__tests__/Collapsible.collapsedHeight.test.tsx +140 -0
- package/src/Collapsible/hooks/useCollapsibleTrigger.ts +3 -1
- package/src/Collapsible/types.ts +89 -22
- package/src/ContextMenu/ContextMenu.tsx +270 -4
- package/src/ContextMenu/types.ts +148 -6
- package/src/DirectionProvider/DirectionProvider.tsx +41 -9
- package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +5 -0
- package/src/DirectionProvider/types.ts +28 -3
- package/src/Divider/Divider.tsx +24 -10
- package/src/Divider/__tests__/Divider.test.tsx +5 -0
- package/src/Divider/types.ts +10 -3
- package/src/Drawer/Drawer.tsx +315 -0
- package/src/Drawer/README.md +124 -0
- package/src/Drawer/__tests__/Drawer.behavior.test.tsx +100 -0
- package/src/Drawer/__tests__/Drawer.focus-trap.test.tsx +55 -0
- package/src/Drawer/__tests__/Drawer.rendering.test.tsx +111 -0
- package/src/Drawer/__tests__/Drawer.side.test.tsx +69 -0
- package/src/Drawer/__tests__/dialog-polyfill.ts +39 -0
- package/src/Drawer/index.ts +2 -0
- package/src/Drawer/types.ts +66 -0
- package/src/Dropdown/Dropdown.tsx +117 -0
- package/src/Dropdown/types.ts +241 -11
- package/src/EmptyState/EmptyState.tsx +74 -4
- package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +7 -2
- package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +3 -1
- package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +10 -0
- package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +3 -1
- package/src/EmptyState/types.ts +49 -6
- package/src/Field/Field.tsx +23 -0
- package/src/Field/__tests__/Field.basic-rendering.test.tsx +11 -0
- package/src/Field/__tests__/Field.error-handling.test.tsx +23 -0
- package/src/Field/__tests__/Field.state-cascade.test.tsx +21 -0
- package/src/Field/hooks/__tests__/useFieldProps.test.tsx +104 -9
- package/src/Field/types.ts +60 -12
- package/src/Fieldset/Fieldset.tsx +54 -18
- package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +9 -0
- package/src/Fieldset/types.ts +20 -2
- package/src/Input/Input.tsx +14 -9
- package/src/Input/__tests__/Input.basic-rendering.test.tsx +5 -0
- package/src/Input/types.ts +19 -2
- package/src/InputGroup/InputGroup.tsx +11 -3
- package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +14 -0
- package/src/InputGroup/types.ts +26 -5
- package/src/MillerColumns/MillerColumns.tsx +86 -19
- package/src/MillerColumns/types.ts +83 -27
- package/src/Modal/Modal.tsx +31 -4
- package/src/Modal/README.md +19 -10
- package/src/Modal/__tests__/Modal.focus-trap.test.tsx +162 -0
- package/src/Modal/hooks/useModalContent.ts +32 -0
- package/src/Modal/types.ts +110 -29
- package/src/NavigationMenu/NavigationMenu.tsx +890 -0
- package/src/NavigationMenu/NavigationMenuContext.ts +67 -0
- package/src/NavigationMenu/README.md +355 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.anchor-positioning.test.tsx +132 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.asChild.test.tsx +73 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.basic-rendering.test.tsx +92 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.contract.test.tsx +45 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.controlled-state.test.tsx +134 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.error-handling.test.tsx +62 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.fixtures.tsx +79 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.forceMount.test.tsx +52 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.hover-interaction.test.tsx +254 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.indicator.test.tsx +252 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-interaction.test.tsx +294 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-navigation.test.tsx +126 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.link.test.tsx +139 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.motion.test.tsx +568 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.mouse-interaction.test.tsx +76 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.uncontrolled-state.test.tsx +30 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.viewport.test.tsx +113 -0
- package/src/NavigationMenu/constants.ts +5 -0
- package/src/NavigationMenu/hooks/index.ts +7 -0
- package/src/NavigationMenu/hooks/useNavigationMenuEntry.ts +33 -0
- package/src/NavigationMenu/hooks/useNavigationMenuIndicator.ts +70 -0
- package/src/NavigationMenu/hooks/useNavigationMenuLink.ts +72 -0
- package/src/NavigationMenu/hooks/useNavigationMenuRoot.ts +301 -0
- package/src/NavigationMenu/hooks/useNavigationMenuTopLevelEntry.ts +65 -0
- package/src/NavigationMenu/hooks/useNavigationMenuTrigger.ts +158 -0
- package/src/NavigationMenu/hooks/useNavigationMenuViewport.ts +101 -0
- package/src/NavigationMenu/index.ts +3 -0
- package/src/NavigationMenu/types.ts +259 -0
- package/src/NavigationMenu/utils.ts +89 -0
- package/src/Popover/Popover.tsx +427 -0
- package/src/Popover/PopoverContext.ts +13 -0
- package/src/Popover/README.md +106 -0
- package/src/Popover/__tests__/Popover.anchor.test.tsx +38 -0
- package/src/Popover/__tests__/Popover.asChild.test.tsx +160 -0
- package/src/Popover/__tests__/Popover.basic-rendering.test.tsx +106 -0
- package/src/Popover/__tests__/Popover.click-outside.test.tsx +149 -0
- package/src/Popover/__tests__/Popover.close.test.tsx +70 -0
- package/src/Popover/__tests__/Popover.content.test.tsx +58 -0
- package/src/Popover/__tests__/Popover.controlled-state.test.tsx +45 -0
- package/src/Popover/__tests__/Popover.error-handling.test.tsx +51 -0
- package/src/Popover/__tests__/Popover.focus.test.tsx +43 -0
- package/src/Popover/__tests__/Popover.keyboard-interaction.test.tsx +71 -0
- package/src/Popover/__tests__/Popover.mouse-interaction.test.tsx +69 -0
- package/src/Popover/__tests__/Popover.uncontrolled-state.test.tsx +44 -0
- package/src/Popover/hooks/index.ts +3 -0
- package/src/Popover/hooks/usePopoverContent.ts +98 -0
- package/src/Popover/hooks/usePopoverRoot.ts +84 -0
- package/src/Popover/hooks/usePopoverTrigger.ts +33 -0
- package/src/Popover/index.ts +2 -0
- package/src/Popover/types.ts +197 -0
- package/src/Portal/Portal.tsx +50 -10
- package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +5 -0
- package/src/Portal/types.ts +5 -0
- package/src/Progress/Progress.tsx +33 -1
- package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +10 -0
- package/src/Progress/__tests__/Progress.value.test.tsx +21 -0
- package/src/Progress/types.ts +20 -4
- package/src/Radio/Radio.tsx +9 -1
- package/src/Radio/__tests__/Radio.basic-rendering.test.tsx +14 -0
- package/src/Radio/hooks/useRadioRoot.ts +3 -0
- package/src/Radio/types.ts +20 -0
- package/src/RadioCard/RadioCard.tsx +11 -0
- package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +8 -0
- package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +27 -0
- package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +53 -0
- package/src/RadioCard/__tests__/RadioCard.ref-forwarding.test.tsx +54 -0
- package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +21 -0
- package/src/RadioCard/hooks/useRadioCardRoot.ts +11 -0
- package/src/RadioCard/types.ts +95 -22
- package/src/RadioGroup/RadioGroup.tsx +11 -0
- package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +8 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +27 -0
- package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +29 -0
- package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +31 -0
- package/src/RadioGroup/hooks/useRadioGroupRoot.ts +11 -0
- package/src/RadioGroup/types.ts +73 -9
- package/src/SegmentedControl/README.md +154 -0
- package/src/SegmentedControl/SegmentedControl.tsx +309 -0
- package/src/SegmentedControl/SegmentedControlContext.ts +28 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.asChild.test.tsx +50 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.basic-rendering.test.tsx +100 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.controlled-state.test.tsx +107 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.disabled-keydown-guards.test.tsx +62 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.disabled.test.tsx +151 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.error-handling.test.tsx +17 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.keyboard-interaction.test.tsx +117 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.orientation.test.tsx +54 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.reading-direction.test.tsx +56 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.ref-forwarding.test.tsx +45 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.roving-tabindex.test.tsx +104 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.uncontrolled-state.test.tsx +94 -0
- package/src/SegmentedControl/hooks/index.ts +2 -0
- package/src/SegmentedControl/hooks/useSegmentedControlContext.ts +1 -0
- package/src/SegmentedControl/hooks/useSegmentedControlRoot.ts +86 -0
- package/src/SegmentedControl/index.ts +2 -0
- package/src/SegmentedControl/types.ts +138 -0
- package/src/Select/README.md +122 -157
- package/src/Select/Select.tsx +595 -68
- package/src/Select/SelectContext.ts +38 -0
- package/src/Select/SelectItemIndicatorContext.ts +29 -0
- package/src/Select/__tests__/Select.asChild.test.tsx +2 -2
- package/src/Select/__tests__/Select.basic-rendering.test.tsx +22 -3
- package/src/Select/__tests__/Select.controlled-state.test.tsx +6 -6
- package/src/Select/__tests__/Select.data-attributes.test.tsx +4 -4
- package/src/Select/__tests__/Select.field-integration.test.tsx +11 -11
- package/src/Select/__tests__/Select.form.test.tsx +38 -0
- package/src/Select/__tests__/Select.group.test.tsx +6 -6
- package/src/Select/__tests__/Select.keyboard-edge.test.tsx +95 -0
- package/src/Select/__tests__/Select.keyboard.test.tsx +124 -0
- package/src/Select/__tests__/Select.mutation-hardening.test.tsx +317 -0
- package/src/Select/__tests__/Select.native-item.test.tsx +30 -0
- package/src/Select/__tests__/Select.open-close.test.tsx +57 -0
- package/src/Select/__tests__/Select.placeholder.test.tsx +4 -4
- package/src/Select/__tests__/Select.rich-asChild.test.tsx +60 -0
- package/src/Select/__tests__/Select.rich-mode.test.tsx +16 -0
- package/src/Select/__tests__/Select.selection.test.tsx +104 -0
- package/src/Select/__tests__/Select.separator.test.tsx +71 -0
- package/src/Select/__tests__/Select.strict-context.test.tsx +36 -0
- package/src/Select/__tests__/Select.typeahead.test.tsx +156 -0
- package/src/Select/__tests__/Select.value-mirror.test.tsx +240 -0
- package/src/Select/constants.ts +8 -0
- package/src/Select/hooks/index.ts +2 -0
- package/src/Select/hooks/useSelectContent.ts +149 -0
- package/src/Select/hooks/useSelectRoot.ts +149 -0
- package/src/Select/types.ts +199 -31
- package/src/SkipNav/SkipNav.tsx +69 -29
- package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +6 -0
- package/src/SkipNav/types.ts +17 -9
- package/src/Slider/Slider.tsx +33 -1
- package/src/Slider/SliderContext.ts +28 -1
- package/src/Slider/types.ts +157 -9
- package/src/Slot/Slot.tsx +94 -19
- package/src/Slot/__tests__/Slot.test.tsx +110 -0
- package/src/Slot/composeEventHandlers.ts +26 -2
- package/src/Slot/types.ts +16 -0
- package/src/Status/Status.tsx +36 -15
- package/src/Status/__tests__/Status.test.tsx +5 -0
- package/src/Status/types.ts +12 -1
- package/src/Switch/Switch.tsx +15 -2
- package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +7 -0
- package/src/Switch/hooks/useSwitchRoot.ts +3 -0
- package/src/Switch/types.ts +49 -22
- package/src/Table/Table.tsx +44 -10
- package/src/Table/types.ts +66 -17
- package/src/Tabs/Tabs.tsx +8 -0
- package/src/Tabs/types.ts +29 -4
- package/src/Textarea/Textarea.tsx +9 -1
- package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +5 -0
- package/src/Textarea/types.ts +20 -3
- package/src/Toggle/Toggle.tsx +51 -13
- package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +5 -0
- package/src/Toggle/types.ts +54 -4
- package/src/ToggleGroup/ToggleGroup.tsx +14 -1
- package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +20 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +9 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.dynamic-items.test.tsx +70 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.error-handling.test.tsx +23 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +23 -1
- package/src/ToggleGroup/__tests__/ToggleGroup.ref-forwarding.test.tsx +58 -0
- package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +21 -1
- package/src/ToggleGroup/types.ts +55 -2
- package/src/Tooltip/Tooltip.tsx +98 -20
- package/src/Tooltip/hooks/__tests__/useTooltipProvider.test.tsx +40 -0
- package/src/Tooltip/hooks/useTooltipProvider.ts +13 -1
- package/src/Tooltip/types.ts +89 -8
- package/src/Tree/Tree.tsx +187 -33
- package/src/Tree/types.ts +54 -12
- package/src/VisuallyHidden/VisuallyHidden.tsx +33 -8
- package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +15 -1
- package/src/VisuallyHidden/types.ts +11 -1
- package/src/index.ts +4 -0
- package/src/test/intersectionObserverPolyfill.ts +7 -0
- package/src/test/resizeObserverPolyfill.ts +80 -0
- package/src/utils/focusable.ts +15 -0
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
|
|
3
|
+
import { Carousel } from "../index.ts";
|
|
4
|
+
|
|
5
|
+
// Click-and-drag scrolling: pointerdown + pointermove sets scrollLeft/
|
|
6
|
+
// scrollTop directly from the pointer delta, amplified by a sensitivity
|
|
7
|
+
// multiplier (no momentum — the multiplier only scales the tracked delta,
|
|
8
|
+
// there's still no motion after release), release lets the existing CSS
|
|
9
|
+
// scroll-snap-type settle to the nearest slide (no new code needed for that
|
|
10
|
+
// half — the scrollsnapchange sync already covers it). A small movement
|
|
11
|
+
// threshold distinguishes a drag from a click so a link/button inside a
|
|
12
|
+
// slide still works. Opt-in via `allowMouseDrag` (default `false`) — an
|
|
13
|
+
// unconditionally-on drag could conflict with a consumer's own
|
|
14
|
+
// drag-sensitive slide content.
|
|
15
|
+
describe("Carousel mouse-drag styling hook (data-mouse-drag)", () => {
|
|
16
|
+
it("should not publish data-mouse-drag when allowMouseDrag is omitted (default false)", () => {
|
|
17
|
+
render(
|
|
18
|
+
<Carousel.Root ariaLabel="Featured products">
|
|
19
|
+
<Carousel.Viewport data-testid="viewport">
|
|
20
|
+
<Carousel.Slide />
|
|
21
|
+
</Carousel.Viewport>
|
|
22
|
+
</Carousel.Root>,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(screen.getByTestId("viewport")).not.toHaveAttribute(
|
|
26
|
+
"data-mouse-drag",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should publish data-mouse-drag when allowMouseDrag is true, as a persistent hook distinct from the transient data-dragging", () => {
|
|
31
|
+
render(
|
|
32
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag>
|
|
33
|
+
<Carousel.Viewport data-testid="viewport">
|
|
34
|
+
<Carousel.Slide />
|
|
35
|
+
</Carousel.Viewport>
|
|
36
|
+
</Carousel.Root>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(screen.getByTestId("viewport")).toHaveAttribute(
|
|
40
|
+
"data-mouse-drag",
|
|
41
|
+
"",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("Carousel mouse-drag scrolling (opt-in disabled)", () => {
|
|
47
|
+
it("should not move scrollLeft or set the dragging hook when allowMouseDrag is omitted (default false)", () => {
|
|
48
|
+
render(
|
|
49
|
+
<Carousel.Root ariaLabel="Featured products">
|
|
50
|
+
<Carousel.Viewport data-testid="viewport">
|
|
51
|
+
<Carousel.Slide />
|
|
52
|
+
<Carousel.Slide />
|
|
53
|
+
</Carousel.Viewport>
|
|
54
|
+
</Carousel.Root>,
|
|
55
|
+
);
|
|
56
|
+
const viewport = screen.getByTestId("viewport");
|
|
57
|
+
Object.defineProperty(viewport, "scrollLeft", {
|
|
58
|
+
value: 100,
|
|
59
|
+
writable: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
act(() => {
|
|
63
|
+
fireEvent.pointerDown(viewport, {
|
|
64
|
+
pointerType: "mouse",
|
|
65
|
+
pointerId: 1,
|
|
66
|
+
clientX: 200,
|
|
67
|
+
clientY: 0,
|
|
68
|
+
});
|
|
69
|
+
fireEvent.pointerMove(viewport, {
|
|
70
|
+
pointerType: "mouse",
|
|
71
|
+
pointerId: 1,
|
|
72
|
+
clientX: 240,
|
|
73
|
+
clientY: 0,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(viewport.scrollLeft).toBe(100);
|
|
78
|
+
expect(viewport).not.toHaveAttribute("data-dragging");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should not move scrollLeft or set the dragging hook when allowMouseDrag is explicitly false", () => {
|
|
82
|
+
render(
|
|
83
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag={false}>
|
|
84
|
+
<Carousel.Viewport data-testid="viewport">
|
|
85
|
+
<Carousel.Slide />
|
|
86
|
+
<Carousel.Slide />
|
|
87
|
+
</Carousel.Viewport>
|
|
88
|
+
</Carousel.Root>,
|
|
89
|
+
);
|
|
90
|
+
const viewport = screen.getByTestId("viewport");
|
|
91
|
+
Object.defineProperty(viewport, "scrollLeft", {
|
|
92
|
+
value: 100,
|
|
93
|
+
writable: true,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
act(() => {
|
|
97
|
+
fireEvent.pointerDown(viewport, {
|
|
98
|
+
pointerType: "mouse",
|
|
99
|
+
pointerId: 1,
|
|
100
|
+
clientX: 200,
|
|
101
|
+
clientY: 0,
|
|
102
|
+
});
|
|
103
|
+
fireEvent.pointerMove(viewport, {
|
|
104
|
+
pointerType: "mouse",
|
|
105
|
+
pointerId: 1,
|
|
106
|
+
clientX: 240,
|
|
107
|
+
clientY: 0,
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(viewport.scrollLeft).toBe(100);
|
|
112
|
+
expect(viewport).not.toHaveAttribute("data-dragging");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("Carousel mouse-drag scrolling (allowMouseDrag enabled)", () => {
|
|
117
|
+
it("should not move scrollLeft for a pointer movement under the threshold", () => {
|
|
118
|
+
render(
|
|
119
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag>
|
|
120
|
+
<Carousel.Viewport data-testid="viewport">
|
|
121
|
+
<Carousel.Slide />
|
|
122
|
+
<Carousel.Slide />
|
|
123
|
+
</Carousel.Viewport>
|
|
124
|
+
</Carousel.Root>,
|
|
125
|
+
);
|
|
126
|
+
const viewport = screen.getByTestId("viewport");
|
|
127
|
+
Object.defineProperty(viewport, "scrollLeft", {
|
|
128
|
+
value: 100,
|
|
129
|
+
writable: true,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
act(() => {
|
|
133
|
+
fireEvent.pointerDown(viewport, {
|
|
134
|
+
pointerType: "mouse",
|
|
135
|
+
pointerId: 1,
|
|
136
|
+
clientX: 200,
|
|
137
|
+
clientY: 0,
|
|
138
|
+
});
|
|
139
|
+
fireEvent.pointerMove(viewport, {
|
|
140
|
+
pointerType: "mouse",
|
|
141
|
+
pointerId: 1,
|
|
142
|
+
clientX: 202,
|
|
143
|
+
clientY: 0,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(viewport.scrollLeft).toBe(100);
|
|
148
|
+
expect(viewport).not.toHaveAttribute("data-dragging");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should start tracking at a 3px movement (the lowered click-vs-drag threshold)", () => {
|
|
152
|
+
render(
|
|
153
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag>
|
|
154
|
+
<Carousel.Viewport data-testid="viewport">
|
|
155
|
+
<Carousel.Slide />
|
|
156
|
+
<Carousel.Slide />
|
|
157
|
+
</Carousel.Viewport>
|
|
158
|
+
</Carousel.Root>,
|
|
159
|
+
);
|
|
160
|
+
const viewport = screen.getByTestId("viewport");
|
|
161
|
+
Object.defineProperty(viewport, "scrollLeft", {
|
|
162
|
+
value: 100,
|
|
163
|
+
writable: true,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
act(() => {
|
|
167
|
+
fireEvent.pointerDown(viewport, {
|
|
168
|
+
pointerType: "mouse",
|
|
169
|
+
pointerId: 1,
|
|
170
|
+
clientX: 200,
|
|
171
|
+
clientY: 0,
|
|
172
|
+
});
|
|
173
|
+
// Exactly 3px — the threshold is now 3, so this crosses it (2px, in
|
|
174
|
+
// the test above, still doesn't).
|
|
175
|
+
fireEvent.pointerMove(viewport, {
|
|
176
|
+
pointerType: "mouse",
|
|
177
|
+
pointerId: 1,
|
|
178
|
+
clientX: 203,
|
|
179
|
+
clientY: 0,
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(viewport).toHaveAttribute("data-dragging", "");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("should scroll faster than the raw pointer delta, amplified by the drag sensitivity multiplier", () => {
|
|
187
|
+
render(
|
|
188
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag>
|
|
189
|
+
<Carousel.Viewport data-testid="viewport">
|
|
190
|
+
<Carousel.Slide />
|
|
191
|
+
<Carousel.Slide />
|
|
192
|
+
</Carousel.Viewport>
|
|
193
|
+
</Carousel.Root>,
|
|
194
|
+
);
|
|
195
|
+
const viewport = screen.getByTestId("viewport");
|
|
196
|
+
Object.defineProperty(viewport, "scrollLeft", {
|
|
197
|
+
value: 100,
|
|
198
|
+
writable: true,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
act(() => {
|
|
202
|
+
fireEvent.pointerDown(viewport, {
|
|
203
|
+
pointerType: "mouse",
|
|
204
|
+
pointerId: 1,
|
|
205
|
+
clientX: 200,
|
|
206
|
+
clientY: 0,
|
|
207
|
+
});
|
|
208
|
+
// Dragging the pointer 40px to the right reveals content to the
|
|
209
|
+
// left — scrollLeft decreases by an amplified amount (2× the raw
|
|
210
|
+
// 40px delta), not the raw delta itself, so a full-slide transition
|
|
211
|
+
// doesn't require dragging the slide's full on-screen width.
|
|
212
|
+
fireEvent.pointerMove(viewport, {
|
|
213
|
+
pointerType: "mouse",
|
|
214
|
+
pointerId: 1,
|
|
215
|
+
clientX: 240,
|
|
216
|
+
clientY: 0,
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(viewport.scrollLeft).toBe(20);
|
|
221
|
+
expect(viewport).toHaveAttribute("data-dragging", "");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should set scrollTop instead of scrollLeft when the carousel is vertical, also amplified", () => {
|
|
225
|
+
render(
|
|
226
|
+
<Carousel.Root ariaLabel="Featured products" orientation="vertical" allowMouseDrag>
|
|
227
|
+
<Carousel.Viewport data-testid="viewport">
|
|
228
|
+
<Carousel.Slide />
|
|
229
|
+
<Carousel.Slide />
|
|
230
|
+
</Carousel.Viewport>
|
|
231
|
+
</Carousel.Root>,
|
|
232
|
+
);
|
|
233
|
+
const viewport = screen.getByTestId("viewport");
|
|
234
|
+
Object.defineProperty(viewport, "scrollTop", {
|
|
235
|
+
value: 100,
|
|
236
|
+
writable: true,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
act(() => {
|
|
240
|
+
fireEvent.pointerDown(viewport, {
|
|
241
|
+
pointerType: "mouse",
|
|
242
|
+
pointerId: 1,
|
|
243
|
+
clientX: 0,
|
|
244
|
+
clientY: 100,
|
|
245
|
+
});
|
|
246
|
+
// 30px raw delta × 2 sensitivity = 60px of scroll.
|
|
247
|
+
fireEvent.pointerMove(viewport, {
|
|
248
|
+
pointerType: "mouse",
|
|
249
|
+
pointerId: 1,
|
|
250
|
+
clientX: 0,
|
|
251
|
+
clientY: 130,
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
expect(viewport.scrollTop).toBe(40);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("should stop tracking and clear the dragging hook on pointerup", () => {
|
|
259
|
+
render(
|
|
260
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag>
|
|
261
|
+
<Carousel.Viewport data-testid="viewport">
|
|
262
|
+
<Carousel.Slide />
|
|
263
|
+
<Carousel.Slide />
|
|
264
|
+
</Carousel.Viewport>
|
|
265
|
+
</Carousel.Root>,
|
|
266
|
+
);
|
|
267
|
+
const viewport = screen.getByTestId("viewport");
|
|
268
|
+
Object.defineProperty(viewport, "scrollLeft", {
|
|
269
|
+
value: 100,
|
|
270
|
+
writable: true,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
act(() => {
|
|
274
|
+
fireEvent.pointerDown(viewport, {
|
|
275
|
+
pointerType: "mouse",
|
|
276
|
+
pointerId: 1,
|
|
277
|
+
clientX: 200,
|
|
278
|
+
clientY: 0,
|
|
279
|
+
});
|
|
280
|
+
fireEvent.pointerMove(viewport, {
|
|
281
|
+
pointerType: "mouse",
|
|
282
|
+
pointerId: 1,
|
|
283
|
+
clientX: 240,
|
|
284
|
+
clientY: 0,
|
|
285
|
+
});
|
|
286
|
+
fireEvent.pointerUp(viewport, { pointerType: "mouse", pointerId: 1 });
|
|
287
|
+
// A move after release must not keep tracking.
|
|
288
|
+
fireEvent.pointerMove(viewport, {
|
|
289
|
+
pointerType: "mouse",
|
|
290
|
+
pointerId: 1,
|
|
291
|
+
clientX: 300,
|
|
292
|
+
clientY: 0,
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(viewport.scrollLeft).toBe(20);
|
|
297
|
+
expect(viewport).not.toHaveAttribute("data-dragging");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("should stop tracking and clear the dragging hook on pointercancel", () => {
|
|
301
|
+
render(
|
|
302
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag>
|
|
303
|
+
<Carousel.Viewport data-testid="viewport">
|
|
304
|
+
<Carousel.Slide />
|
|
305
|
+
<Carousel.Slide />
|
|
306
|
+
</Carousel.Viewport>
|
|
307
|
+
</Carousel.Root>,
|
|
308
|
+
);
|
|
309
|
+
const viewport = screen.getByTestId("viewport");
|
|
310
|
+
Object.defineProperty(viewport, "scrollLeft", {
|
|
311
|
+
value: 100,
|
|
312
|
+
writable: true,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
act(() => {
|
|
316
|
+
fireEvent.pointerDown(viewport, {
|
|
317
|
+
pointerType: "mouse",
|
|
318
|
+
pointerId: 1,
|
|
319
|
+
clientX: 200,
|
|
320
|
+
clientY: 0,
|
|
321
|
+
});
|
|
322
|
+
fireEvent.pointerMove(viewport, {
|
|
323
|
+
pointerType: "mouse",
|
|
324
|
+
pointerId: 1,
|
|
325
|
+
clientX: 240,
|
|
326
|
+
clientY: 0,
|
|
327
|
+
});
|
|
328
|
+
fireEvent.pointerCancel(viewport, {
|
|
329
|
+
pointerType: "mouse",
|
|
330
|
+
pointerId: 1,
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
expect(viewport).not.toHaveAttribute("data-dragging");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("should no-op a pointerup with no preceding pointerdown-tracked drag", () => {
|
|
338
|
+
render(
|
|
339
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag>
|
|
340
|
+
<Carousel.Viewport data-testid="viewport">
|
|
341
|
+
<Carousel.Slide />
|
|
342
|
+
<Carousel.Slide />
|
|
343
|
+
</Carousel.Viewport>
|
|
344
|
+
</Carousel.Root>,
|
|
345
|
+
);
|
|
346
|
+
const viewport = screen.getByTestId("viewport");
|
|
347
|
+
|
|
348
|
+
expect(() => {
|
|
349
|
+
act(() => {
|
|
350
|
+
fireEvent.pointerUp(viewport, { pointerType: "mouse", pointerId: 1 });
|
|
351
|
+
});
|
|
352
|
+
}).not.toThrow();
|
|
353
|
+
expect(viewport).not.toHaveAttribute("data-dragging");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("should ignore non-mouse pointer types (native touch scroll already handles them)", () => {
|
|
357
|
+
render(
|
|
358
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag>
|
|
359
|
+
<Carousel.Viewport data-testid="viewport">
|
|
360
|
+
<Carousel.Slide />
|
|
361
|
+
<Carousel.Slide />
|
|
362
|
+
</Carousel.Viewport>
|
|
363
|
+
</Carousel.Root>,
|
|
364
|
+
);
|
|
365
|
+
const viewport = screen.getByTestId("viewport");
|
|
366
|
+
Object.defineProperty(viewport, "scrollLeft", {
|
|
367
|
+
value: 100,
|
|
368
|
+
writable: true,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
act(() => {
|
|
372
|
+
fireEvent.pointerDown(viewport, {
|
|
373
|
+
pointerType: "touch",
|
|
374
|
+
pointerId: 1,
|
|
375
|
+
clientX: 200,
|
|
376
|
+
clientY: 0,
|
|
377
|
+
});
|
|
378
|
+
fireEvent.pointerMove(viewport, {
|
|
379
|
+
pointerType: "touch",
|
|
380
|
+
pointerId: 1,
|
|
381
|
+
clientX: 240,
|
|
382
|
+
clientY: 0,
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
expect(viewport.scrollLeft).toBe(100);
|
|
387
|
+
expect(viewport).not.toHaveAttribute("data-dragging");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("should suppress the click that follows a real drag, so a link under the pointer doesn't fire", () => {
|
|
391
|
+
const onClick = vi.fn();
|
|
392
|
+
render(
|
|
393
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag>
|
|
394
|
+
<Carousel.Viewport data-testid="viewport">
|
|
395
|
+
<Carousel.Slide>
|
|
396
|
+
<button type="button" onClick={onClick}>
|
|
397
|
+
Inner
|
|
398
|
+
</button>
|
|
399
|
+
</Carousel.Slide>
|
|
400
|
+
</Carousel.Viewport>
|
|
401
|
+
</Carousel.Root>,
|
|
402
|
+
);
|
|
403
|
+
const viewport = screen.getByTestId("viewport");
|
|
404
|
+
Object.defineProperty(viewport, "scrollLeft", {
|
|
405
|
+
value: 100,
|
|
406
|
+
writable: true,
|
|
407
|
+
});
|
|
408
|
+
const button = screen.getByRole("button", { name: "Inner" });
|
|
409
|
+
|
|
410
|
+
act(() => {
|
|
411
|
+
fireEvent.pointerDown(viewport, {
|
|
412
|
+
pointerType: "mouse",
|
|
413
|
+
pointerId: 1,
|
|
414
|
+
clientX: 200,
|
|
415
|
+
clientY: 0,
|
|
416
|
+
});
|
|
417
|
+
fireEvent.pointerMove(viewport, {
|
|
418
|
+
pointerType: "mouse",
|
|
419
|
+
pointerId: 1,
|
|
420
|
+
clientX: 240,
|
|
421
|
+
clientY: 0,
|
|
422
|
+
});
|
|
423
|
+
fireEvent.pointerUp(viewport, { pointerType: "mouse", pointerId: 1 });
|
|
424
|
+
// Browsers still synthesize a click at the release point after a
|
|
425
|
+
// drag unless it's suppressed.
|
|
426
|
+
fireEvent.click(button);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("should not suppress a plain click that never crossed the drag threshold", async () => {
|
|
433
|
+
const onClick = vi.fn();
|
|
434
|
+
render(
|
|
435
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag>
|
|
436
|
+
<Carousel.Viewport data-testid="viewport">
|
|
437
|
+
<Carousel.Slide>
|
|
438
|
+
<button type="button" onClick={onClick}>
|
|
439
|
+
Inner
|
|
440
|
+
</button>
|
|
441
|
+
</Carousel.Slide>
|
|
442
|
+
</Carousel.Viewport>
|
|
443
|
+
</Carousel.Root>,
|
|
444
|
+
);
|
|
445
|
+
const viewport = screen.getByTestId("viewport");
|
|
446
|
+
const button = screen.getByRole("button", { name: "Inner" });
|
|
447
|
+
|
|
448
|
+
act(() => {
|
|
449
|
+
fireEvent.pointerDown(viewport, {
|
|
450
|
+
pointerType: "mouse",
|
|
451
|
+
pointerId: 1,
|
|
452
|
+
clientX: 200,
|
|
453
|
+
clientY: 0,
|
|
454
|
+
});
|
|
455
|
+
fireEvent.pointerUp(viewport, { pointerType: "mouse", pointerId: 1 });
|
|
456
|
+
fireEvent.click(button);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
describe("Carousel native image-drag suppression (onDragStart)", () => {
|
|
464
|
+
it("should prevent the browser's own image-drag ghost when allowMouseDrag is enabled", () => {
|
|
465
|
+
render(
|
|
466
|
+
<Carousel.Root ariaLabel="Featured products" allowMouseDrag>
|
|
467
|
+
<Carousel.Viewport data-testid="viewport">
|
|
468
|
+
<Carousel.Slide>
|
|
469
|
+
<img src="photo.jpg" alt="Slide photo" />
|
|
470
|
+
</Carousel.Slide>
|
|
471
|
+
</Carousel.Viewport>
|
|
472
|
+
</Carousel.Root>,
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
// Real <img>/<a> elements are natively draggable, and a dragstart on one
|
|
476
|
+
// bubbles up to the viewport, where our own handler lives — the browser's
|
|
477
|
+
// HTML5 drag (the semi-transparent ghost that follows the cursor) must
|
|
478
|
+
// never compete with the custom pointer-based drag-to-scroll. fireEvent
|
|
479
|
+
// returns dispatchEvent's result: false once preventDefault is called.
|
|
480
|
+
const dispatchResult = fireEvent.dragStart(
|
|
481
|
+
screen.getByRole("img", { name: "Slide photo" }),
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
expect(dispatchResult).toBe(false);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it("should leave native image drag alone when allowMouseDrag is omitted (default false)", () => {
|
|
488
|
+
render(
|
|
489
|
+
<Carousel.Root ariaLabel="Featured products">
|
|
490
|
+
<Carousel.Viewport data-testid="viewport">
|
|
491
|
+
<Carousel.Slide>
|
|
492
|
+
<img src="photo.jpg" alt="Slide photo" />
|
|
493
|
+
</Carousel.Slide>
|
|
494
|
+
</Carousel.Viewport>
|
|
495
|
+
</Carousel.Root>,
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
const dispatchResult = fireEvent.dragStart(
|
|
499
|
+
screen.getByRole("img", { name: "Slide photo" }),
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
expect(dispatchResult).toBe(true);
|
|
503
|
+
});
|
|
504
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
|
|
3
|
+
import { Carousel } from "../index.ts";
|
|
4
|
+
|
|
5
|
+
// A carousel's slide count and layout counts come from the consumer, so a
|
|
6
|
+
// bad slidesPerPage / slidesPerMove (0, negative, NaN, fractional) must
|
|
7
|
+
// degrade gracefully to a sane integer ≥ 1 rather than dividing by zero
|
|
8
|
+
// (Infinity pages → a thrown RangeError from the indicator map) or going
|
|
9
|
+
// silently inert.
|
|
10
|
+
describe("Carousel multi-slide input guards", () => {
|
|
11
|
+
it("should treat slidesPerPage < 1 as 1 rather than dividing by zero", () => {
|
|
12
|
+
render(
|
|
13
|
+
<Carousel.Root ariaLabel="Featured products" slidesPerPage={0}>
|
|
14
|
+
<Carousel.Viewport>
|
|
15
|
+
<Carousel.Slide />
|
|
16
|
+
<Carousel.Slide />
|
|
17
|
+
<Carousel.Slide />
|
|
18
|
+
<Carousel.Slide />
|
|
19
|
+
</Carousel.Viewport>
|
|
20
|
+
<Carousel.Indicators label="Choose page" />
|
|
21
|
+
</Carousel.Root>,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// perPage coerced to 1 → one page per slide → 4 indicators.
|
|
25
|
+
expect(
|
|
26
|
+
screen.getAllByRole("button", { name: /^Slide \d+$/ }),
|
|
27
|
+
).toHaveLength(4);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should treat a non-finite slidesPerPage as 1", () => {
|
|
31
|
+
render(
|
|
32
|
+
<Carousel.Root ariaLabel="Featured products" slidesPerPage={NaN}>
|
|
33
|
+
<Carousel.Viewport>
|
|
34
|
+
<Carousel.Slide />
|
|
35
|
+
<Carousel.Slide />
|
|
36
|
+
<Carousel.Slide />
|
|
37
|
+
</Carousel.Viewport>
|
|
38
|
+
<Carousel.Indicators label="Choose page" />
|
|
39
|
+
</Carousel.Root>,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(
|
|
43
|
+
screen.getAllByRole("button", { name: /^Slide \d+$/ }),
|
|
44
|
+
).toHaveLength(3);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should round a fractional slidesPerPage down to an integer", () => {
|
|
48
|
+
render(
|
|
49
|
+
<Carousel.Root ariaLabel="Featured products" slidesPerPage={2.5}>
|
|
50
|
+
<Carousel.Viewport>
|
|
51
|
+
<Carousel.Slide />
|
|
52
|
+
<Carousel.Slide />
|
|
53
|
+
<Carousel.Slide />
|
|
54
|
+
<Carousel.Slide />
|
|
55
|
+
<Carousel.Slide />
|
|
56
|
+
</Carousel.Viewport>
|
|
57
|
+
<Carousel.Indicators label="Choose page" />
|
|
58
|
+
</Carousel.Root>,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// floor(2.5) = 2 → ceil(5 / 2) = 3 pages (an unfloored 2.5 would give
|
|
62
|
+
// ceil(5 / 2.5) = 2, so this pins the integer coercion).
|
|
63
|
+
expect(
|
|
64
|
+
screen.getAllByRole("button", { name: /^Slide \d+$/ }),
|
|
65
|
+
).toHaveLength(3);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should treat a numeric slidesPerMove < 1 as 1", () => {
|
|
69
|
+
render(
|
|
70
|
+
<Carousel.Root
|
|
71
|
+
ariaLabel="Featured products"
|
|
72
|
+
slidesPerPage={2}
|
|
73
|
+
slidesPerMove={0}
|
|
74
|
+
>
|
|
75
|
+
<Carousel.Viewport>
|
|
76
|
+
<Carousel.Slide />
|
|
77
|
+
<Carousel.Slide />
|
|
78
|
+
<Carousel.Slide />
|
|
79
|
+
<Carousel.Slide />
|
|
80
|
+
</Carousel.Viewport>
|
|
81
|
+
<Carousel.Indicators label="Choose page" />
|
|
82
|
+
</Carousel.Root>,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// move coerced to 1 → windows [0,1] [1,2] [2,3] → 3 pages (not the
|
|
86
|
+
// Infinity a move of 0 would divide to).
|
|
87
|
+
expect(
|
|
88
|
+
screen.getAllByRole("button", { name: /^Slide \d+$/ }),
|
|
89
|
+
).toHaveLength(3);
|
|
90
|
+
});
|
|
91
|
+
});
|