@leonsilicon/react-native-reanimated-carousel 0.0.0
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/LICENSE +21 -0
- package/README.md +652 -0
- package/lib/commonjs/components/Carousel.js +2 -0
- package/lib/commonjs/components/Carousel.js.map +1 -0
- package/lib/commonjs/components/CarouselLayout.js +2 -0
- package/lib/commonjs/components/CarouselLayout.js.map +1 -0
- package/lib/commonjs/components/ItemLayout.js +2 -0
- package/lib/commonjs/components/ItemLayout.js.map +1 -0
- package/lib/commonjs/components/ItemRenderer.js +2 -0
- package/lib/commonjs/components/ItemRenderer.js.map +1 -0
- package/lib/commonjs/components/LazyView.js +2 -0
- package/lib/commonjs/components/LazyView.js.map +1 -0
- package/lib/commonjs/components/Pagination/Basic/PaginationItem.js +2 -0
- package/lib/commonjs/components/Pagination/Basic/PaginationItem.js.map +1 -0
- package/lib/commonjs/components/Pagination/Basic/index.js +2 -0
- package/lib/commonjs/components/Pagination/Basic/index.js.map +1 -0
- package/lib/commonjs/components/Pagination/Custom/PaginationItem.js +2 -0
- package/lib/commonjs/components/Pagination/Custom/PaginationItem.js.map +1 -0
- package/lib/commonjs/components/Pagination/Custom/index.js +2 -0
- package/lib/commonjs/components/Pagination/Custom/index.js.map +1 -0
- package/lib/commonjs/components/Pagination/index.js +2 -0
- package/lib/commonjs/components/Pagination/index.js.map +1 -0
- package/lib/commonjs/components/ScrollViewGesture.js +2 -0
- package/lib/commonjs/components/ScrollViewGesture.js.map +1 -0
- package/lib/commonjs/constants/index.js +2 -0
- package/lib/commonjs/constants/index.js.map +1 -0
- package/lib/commonjs/hooks/useAutoPlay.js +2 -0
- package/lib/commonjs/hooks/useAutoPlay.js.map +1 -0
- package/lib/commonjs/hooks/useCarouselController.js +2 -0
- package/lib/commonjs/hooks/useCarouselController.js.map +1 -0
- package/lib/commonjs/hooks/useCheckMounted.js +2 -0
- package/lib/commonjs/hooks/useCheckMounted.js.map +1 -0
- package/lib/commonjs/hooks/useCommonVariables.js +2 -0
- package/lib/commonjs/hooks/useCommonVariables.js.map +1 -0
- package/lib/commonjs/hooks/useInitProps.js +2 -0
- package/lib/commonjs/hooks/useInitProps.js.map +1 -0
- package/lib/commonjs/hooks/useLayoutConfig.js +2 -0
- package/lib/commonjs/hooks/useLayoutConfig.js.map +1 -0
- package/lib/commonjs/hooks/useOffsetX.js +2 -0
- package/lib/commonjs/hooks/useOffsetX.js.map +1 -0
- package/lib/commonjs/hooks/useOnProgressChange.js +2 -0
- package/lib/commonjs/hooks/useOnProgressChange.js.map +1 -0
- package/lib/commonjs/hooks/usePanGestureProxy.js +2 -0
- package/lib/commonjs/hooks/usePanGestureProxy.js.map +1 -0
- package/lib/commonjs/hooks/usePropsErrorBoundary.js +2 -0
- package/lib/commonjs/hooks/usePropsErrorBoundary.js.map +1 -0
- package/lib/commonjs/hooks/useSizeResolver.js +2 -0
- package/lib/commonjs/hooks/useSizeResolver.js.map +1 -0
- package/lib/commonjs/hooks/useUpdateGestureConfig.js +2 -0
- package/lib/commonjs/hooks/useUpdateGestureConfig.js.map +1 -0
- package/lib/commonjs/hooks/useVisibleRanges.js +2 -0
- package/lib/commonjs/hooks/useVisibleRanges.js.map +1 -0
- package/lib/commonjs/index.js +2 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/layouts/index.js +2 -0
- package/lib/commonjs/layouts/index.js.map +1 -0
- package/lib/commonjs/layouts/normal.js +2 -0
- package/lib/commonjs/layouts/normal.js.map +1 -0
- package/lib/commonjs/layouts/parallax.js +2 -0
- package/lib/commonjs/layouts/parallax.js.map +1 -0
- package/lib/commonjs/layouts/stack.js +2 -0
- package/lib/commonjs/layouts/stack.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/store/index.js +2 -0
- package/lib/commonjs/store/index.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/utils/compute-gesture-translation.js +2 -0
- package/lib/commonjs/utils/compute-gesture-translation.js.map +1 -0
- package/lib/commonjs/utils/compute-offset-if-data-changed.js +2 -0
- package/lib/commonjs/utils/compute-offset-if-data-changed.js.map +1 -0
- package/lib/commonjs/utils/compute-offset-if-size-changed.js +2 -0
- package/lib/commonjs/utils/compute-offset-if-size-changed.js.map +1 -0
- package/lib/commonjs/utils/compute-offset-if-sizes-changed.js +2 -0
- package/lib/commonjs/utils/compute-offset-if-sizes-changed.js.map +1 -0
- package/lib/commonjs/utils/computed-with-auto-fill-data.js +2 -0
- package/lib/commonjs/utils/computed-with-auto-fill-data.js.map +1 -0
- package/lib/commonjs/utils/deal-with-animation.js +2 -0
- package/lib/commonjs/utils/deal-with-animation.js.map +1 -0
- package/lib/commonjs/utils/handleroffset-direction.js +2 -0
- package/lib/commonjs/utils/handleroffset-direction.js.map +1 -0
- package/lib/commonjs/utils/log.js +2 -0
- package/lib/commonjs/utils/log.js.map +1 -0
- package/lib/commonjs/utils/sanitize-animation-style.js +2 -0
- package/lib/commonjs/utils/sanitize-animation-style.js.map +1 -0
- package/lib/commonjs/utils/size-resolver.js +2 -0
- package/lib/commonjs/utils/size-resolver.js.map +1 -0
- package/lib/module/components/Carousel.js +2 -0
- package/lib/module/components/Carousel.js.map +1 -0
- package/lib/module/components/CarouselLayout.js +2 -0
- package/lib/module/components/CarouselLayout.js.map +1 -0
- package/lib/module/components/ItemLayout.js +2 -0
- package/lib/module/components/ItemLayout.js.map +1 -0
- package/lib/module/components/ItemRenderer.js +2 -0
- package/lib/module/components/ItemRenderer.js.map +1 -0
- package/lib/module/components/LazyView.js +2 -0
- package/lib/module/components/LazyView.js.map +1 -0
- package/lib/module/components/Pagination/Basic/PaginationItem.js +2 -0
- package/lib/module/components/Pagination/Basic/PaginationItem.js.map +1 -0
- package/lib/module/components/Pagination/Basic/index.js +2 -0
- package/lib/module/components/Pagination/Basic/index.js.map +1 -0
- package/lib/module/components/Pagination/Custom/PaginationItem.js +2 -0
- package/lib/module/components/Pagination/Custom/PaginationItem.js.map +1 -0
- package/lib/module/components/Pagination/Custom/index.js +2 -0
- package/lib/module/components/Pagination/Custom/index.js.map +1 -0
- package/lib/module/components/Pagination/index.js +2 -0
- package/lib/module/components/Pagination/index.js.map +1 -0
- package/lib/module/components/ScrollViewGesture.js +2 -0
- package/lib/module/components/ScrollViewGesture.js.map +1 -0
- package/lib/module/constants/index.js +2 -0
- package/lib/module/constants/index.js.map +1 -0
- package/lib/module/hooks/useAutoPlay.js +2 -0
- package/lib/module/hooks/useAutoPlay.js.map +1 -0
- package/lib/module/hooks/useCarouselController.js +2 -0
- package/lib/module/hooks/useCarouselController.js.map +1 -0
- package/lib/module/hooks/useCheckMounted.js +2 -0
- package/lib/module/hooks/useCheckMounted.js.map +1 -0
- package/lib/module/hooks/useCommonVariables.js +2 -0
- package/lib/module/hooks/useCommonVariables.js.map +1 -0
- package/lib/module/hooks/useInitProps.js +2 -0
- package/lib/module/hooks/useInitProps.js.map +1 -0
- package/lib/module/hooks/useLayoutConfig.js +2 -0
- package/lib/module/hooks/useLayoutConfig.js.map +1 -0
- package/lib/module/hooks/useOffsetX.js +2 -0
- package/lib/module/hooks/useOffsetX.js.map +1 -0
- package/lib/module/hooks/useOnProgressChange.js +2 -0
- package/lib/module/hooks/useOnProgressChange.js.map +1 -0
- package/lib/module/hooks/usePanGestureProxy.js +2 -0
- package/lib/module/hooks/usePanGestureProxy.js.map +1 -0
- package/lib/module/hooks/usePropsErrorBoundary.js +2 -0
- package/lib/module/hooks/usePropsErrorBoundary.js.map +1 -0
- package/lib/module/hooks/useSizeResolver.js +2 -0
- package/lib/module/hooks/useSizeResolver.js.map +1 -0
- package/lib/module/hooks/useUpdateGestureConfig.js +2 -0
- package/lib/module/hooks/useUpdateGestureConfig.js.map +1 -0
- package/lib/module/hooks/useVisibleRanges.js +2 -0
- package/lib/module/hooks/useVisibleRanges.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/layouts/index.js +2 -0
- package/lib/module/layouts/index.js.map +1 -0
- package/lib/module/layouts/normal.js +2 -0
- package/lib/module/layouts/normal.js.map +1 -0
- package/lib/module/layouts/parallax.js +2 -0
- package/lib/module/layouts/parallax.js.map +1 -0
- package/lib/module/layouts/stack.js +2 -0
- package/lib/module/layouts/stack.js.map +1 -0
- package/lib/module/store/index.js +2 -0
- package/lib/module/store/index.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/compute-gesture-translation.js +2 -0
- package/lib/module/utils/compute-gesture-translation.js.map +1 -0
- package/lib/module/utils/compute-offset-if-data-changed.js +2 -0
- package/lib/module/utils/compute-offset-if-data-changed.js.map +1 -0
- package/lib/module/utils/compute-offset-if-size-changed.js +2 -0
- package/lib/module/utils/compute-offset-if-size-changed.js.map +1 -0
- package/lib/module/utils/compute-offset-if-sizes-changed.js +2 -0
- package/lib/module/utils/compute-offset-if-sizes-changed.js.map +1 -0
- package/lib/module/utils/computed-with-auto-fill-data.js +2 -0
- package/lib/module/utils/computed-with-auto-fill-data.js.map +1 -0
- package/lib/module/utils/deal-with-animation.js +2 -0
- package/lib/module/utils/deal-with-animation.js.map +1 -0
- package/lib/module/utils/handleroffset-direction.js +2 -0
- package/lib/module/utils/handleroffset-direction.js.map +1 -0
- package/lib/module/utils/log.js +2 -0
- package/lib/module/utils/log.js.map +1 -0
- package/lib/module/utils/sanitize-animation-style.js +2 -0
- package/lib/module/utils/sanitize-animation-style.js.map +1 -0
- package/lib/module/utils/size-resolver.js +2 -0
- package/lib/module/utils/size-resolver.js.map +1 -0
- package/lib/typescript/components/Carousel.d.ts +8 -0
- package/lib/typescript/components/Carousel.d.ts.map +1 -0
- package/lib/typescript/components/CarouselLayout.d.ts +6 -0
- package/lib/typescript/components/CarouselLayout.d.ts.map +1 -0
- package/lib/typescript/components/ItemLayout.d.ts +15 -0
- package/lib/typescript/components/ItemLayout.d.ts.map +1 -0
- package/lib/typescript/components/ItemRenderer.d.ts +24 -0
- package/lib/typescript/components/ItemRenderer.d.ts.map +1 -0
- package/lib/typescript/components/LazyView.d.ts +8 -0
- package/lib/typescript/components/LazyView.d.ts.map +1 -0
- package/lib/typescript/components/Pagination/Basic/PaginationItem.d.ts +29 -0
- package/lib/typescript/components/Pagination/Basic/PaginationItem.d.ts.map +1 -0
- package/lib/typescript/components/Pagination/Basic/index.d.ts +23 -0
- package/lib/typescript/components/Pagination/Basic/index.d.ts.map +1 -0
- package/lib/typescript/components/Pagination/Custom/PaginationItem.d.ts +35 -0
- package/lib/typescript/components/Pagination/Custom/PaginationItem.d.ts.map +1 -0
- package/lib/typescript/components/Pagination/Custom/index.d.ts +26 -0
- package/lib/typescript/components/Pagination/Custom/index.d.ts.map +1 -0
- package/lib/typescript/components/Pagination/index.d.ts +5 -0
- package/lib/typescript/components/Pagination/index.d.ts.map +1 -0
- package/lib/typescript/components/ScrollViewGesture.d.ts +19 -0
- package/lib/typescript/components/ScrollViewGesture.d.ts.map +1 -0
- package/lib/typescript/constants/index.d.ts +9 -0
- package/lib/typescript/constants/index.d.ts.map +1 -0
- package/lib/typescript/hooks/useAutoPlay.d.ts +12 -0
- package/lib/typescript/hooks/useAutoPlay.d.ts.map +1 -0
- package/lib/typescript/hooks/useCarouselController.d.ts +28 -0
- package/lib/typescript/hooks/useCarouselController.d.ts.map +1 -0
- package/lib/typescript/hooks/useCheckMounted.d.ts +3 -0
- package/lib/typescript/hooks/useCheckMounted.d.ts.map +1 -0
- package/lib/typescript/hooks/useCommonVariables.d.ts +13 -0
- package/lib/typescript/hooks/useCommonVariables.d.ts.map +1 -0
- package/lib/typescript/hooks/useInitProps.d.ts +17 -0
- package/lib/typescript/hooks/useInitProps.d.ts.map +1 -0
- package/lib/typescript/hooks/useLayoutConfig.d.ts +10 -0
- package/lib/typescript/hooks/useLayoutConfig.d.ts.map +1 -0
- package/lib/typescript/hooks/useOffsetX.d.ts +24 -0
- package/lib/typescript/hooks/useOffsetX.d.ts.map +1 -0
- package/lib/typescript/hooks/useOnProgressChange.d.ts +14 -0
- package/lib/typescript/hooks/useOnProgressChange.d.ts.map +1 -0
- package/lib/typescript/hooks/usePanGestureProxy.d.ts +10 -0
- package/lib/typescript/hooks/usePanGestureProxy.d.ts.map +1 -0
- package/lib/typescript/hooks/usePropsErrorBoundary.d.ts +5 -0
- package/lib/typescript/hooks/usePropsErrorBoundary.d.ts.map +1 -0
- package/lib/typescript/hooks/useSizeResolver.d.ts +27 -0
- package/lib/typescript/hooks/useSizeResolver.d.ts.map +1 -0
- package/lib/typescript/hooks/useUpdateGestureConfig.d.ts +6 -0
- package/lib/typescript/hooks/useUpdateGestureConfig.d.ts.map +1 -0
- package/lib/typescript/hooks/useVisibleRanges.d.ts +23 -0
- package/lib/typescript/hooks/useVisibleRanges.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +7 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/layouts/index.d.ts +11 -0
- package/lib/typescript/layouts/index.d.ts.map +1 -0
- package/lib/typescript/layouts/normal.d.ts +16 -0
- package/lib/typescript/layouts/normal.d.ts.map +1 -0
- package/lib/typescript/layouts/parallax.d.ts +50 -0
- package/lib/typescript/layouts/parallax.d.ts.map +1 -0
- package/lib/typescript/layouts/stack.d.ts +38 -0
- package/lib/typescript/layouts/stack.d.ts.map +1 -0
- package/lib/typescript/store/index.d.ts +38 -0
- package/lib/typescript/store/index.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +326 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/utils/compute-gesture-translation.d.ts +9 -0
- package/lib/typescript/utils/compute-gesture-translation.d.ts.map +1 -0
- package/lib/typescript/utils/compute-offset-if-data-changed.d.ts +9 -0
- package/lib/typescript/utils/compute-offset-if-data-changed.d.ts.map +1 -0
- package/lib/typescript/utils/compute-offset-if-size-changed.d.ts +6 -0
- package/lib/typescript/utils/compute-offset-if-size-changed.d.ts.map +1 -0
- package/lib/typescript/utils/compute-offset-if-sizes-changed.d.ts +14 -0
- package/lib/typescript/utils/compute-offset-if-sizes-changed.d.ts.map +1 -0
- package/lib/typescript/utils/computed-with-auto-fill-data.d.ts +23 -0
- package/lib/typescript/utils/computed-with-auto-fill-data.d.ts.map +1 -0
- package/lib/typescript/utils/deal-with-animation.d.ts +3 -0
- package/lib/typescript/utils/deal-with-animation.d.ts.map +1 -0
- package/lib/typescript/utils/handleroffset-direction.d.ts +4 -0
- package/lib/typescript/utils/handleroffset-direction.d.ts.map +1 -0
- package/lib/typescript/utils/log.d.ts +7 -0
- package/lib/typescript/utils/log.d.ts.map +1 -0
- package/lib/typescript/utils/sanitize-animation-style.d.ts +3 -0
- package/lib/typescript/utils/sanitize-animation-style.d.ts.map +1 -0
- package/lib/typescript/utils/size-resolver.d.ts +87 -0
- package/lib/typescript/utils/size-resolver.d.ts.map +1 -0
- package/package.json +151 -0
- package/src/components/Carousel.test.tsx +1153 -0
- package/src/components/Carousel.tsx +35 -0
- package/src/components/CarouselLayout.tsx +231 -0
- package/src/components/ItemLayout.tsx +217 -0
- package/src/components/ItemRenderer.tsx +114 -0
- package/src/components/LazyView.test.tsx +61 -0
- package/src/components/LazyView.tsx +14 -0
- package/src/components/Pagination/Basic/PaginationItem.tsx +149 -0
- package/src/components/Pagination/Basic/index.tsx +98 -0
- package/src/components/Pagination/Custom/PaginationItem.tsx +166 -0
- package/src/components/Pagination/Custom/index.tsx +111 -0
- package/src/components/Pagination/Pagination.test.tsx +178 -0
- package/src/components/Pagination/index.tsx +7 -0
- package/src/components/ScrollViewGesture.tsx +577 -0
- package/src/components/rnr-demo.test.tsx +53 -0
- package/src/constants/index.ts +11 -0
- package/src/hooks/useAutoPlay.test.ts +194 -0
- package/src/hooks/useAutoPlay.ts +58 -0
- package/src/hooks/useCarouselController.test.tsx +1158 -0
- package/src/hooks/useCarouselController.tsx +525 -0
- package/src/hooks/useCheckMounted.test.ts +47 -0
- package/src/hooks/useCheckMounted.ts +14 -0
- package/src/hooks/useCommonVariables.test.tsx +384 -0
- package/src/hooks/useCommonVariables.ts +202 -0
- package/src/hooks/useInitProps.test.tsx +134 -0
- package/src/hooks/useInitProps.ts +111 -0
- package/src/hooks/useLayoutConfig.test.tsx +247 -0
- package/src/hooks/useLayoutConfig.ts +30 -0
- package/src/hooks/useOffsetX.test.ts +110 -0
- package/src/hooks/useOffsetX.ts +109 -0
- package/src/hooks/useOnProgressChange.test.tsx +207 -0
- package/src/hooks/useOnProgressChange.ts +105 -0
- package/src/hooks/usePanGestureProxy.test.tsx +368 -0
- package/src/hooks/usePanGestureProxy.ts +144 -0
- package/src/hooks/usePropsErrorBoundary.ts +138 -0
- package/src/hooks/useSizeResolver.test.tsx +112 -0
- package/src/hooks/useSizeResolver.ts +106 -0
- package/src/hooks/useUpdateGestureConfig.test.ts +89 -0
- package/src/hooks/useUpdateGestureConfig.ts +14 -0
- package/src/hooks/useVisibleRanges.test.tsx +366 -0
- package/src/hooks/useVisibleRanges.tsx +123 -0
- package/src/index.tsx +13 -0
- package/src/layouts/index.tsx +12 -0
- package/src/layouts/normal.ts +32 -0
- package/src/layouts/parallax.test.ts +239 -0
- package/src/layouts/parallax.ts +83 -0
- package/src/layouts/stack.test.ts +252 -0
- package/src/layouts/stack.ts +306 -0
- package/src/store/index.test.tsx +314 -0
- package/src/store/index.tsx +66 -0
- package/src/types.ts +348 -0
- package/src/utils/compute-gesture-translation.test.ts +70 -0
- package/src/utils/compute-gesture-translation.ts +29 -0
- package/src/utils/compute-offset-if-data-changed.test.ts +133 -0
- package/src/utils/compute-offset-if-data-changed.ts +44 -0
- package/src/utils/compute-offset-if-size-changed.test.ts +78 -0
- package/src/utils/compute-offset-if-size-changed.ts +14 -0
- package/src/utils/compute-offset-if-sizes-changed.test.ts +74 -0
- package/src/utils/compute-offset-if-sizes-changed.ts +44 -0
- package/src/utils/computed-with-auto-fill-data.test.ts +298 -0
- package/src/utils/computed-with-auto-fill-data.ts +92 -0
- package/src/utils/deal-with-animation.test.ts +181 -0
- package/src/utils/deal-with-animation.ts +17 -0
- package/src/utils/handleroffset-direction.test.ts +124 -0
- package/src/utils/handleroffset-direction.ts +18 -0
- package/src/utils/index.test.ts +90 -0
- package/src/utils/log.test.ts +134 -0
- package/src/utils/log.ts +12 -0
- package/src/utils/sanitize-animation-style.test.ts +40 -0
- package/src/utils/sanitize-animation-style.ts +20 -0
- package/src/utils/size-resolver.test.ts +193 -0
- package/src/utils/size-resolver.ts +216 -0
|
@@ -0,0 +1,1153 @@
|
|
|
1
|
+
import type { FC } from "react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import type { PanGesture } from "react-native-gesture-handler";
|
|
4
|
+
import { Gesture, State } from "react-native-gesture-handler";
|
|
5
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
6
|
+
import Animated, { interpolate, useDerivedValue, useSharedValue } from "react-native-reanimated";
|
|
7
|
+
import type { ReactTestInstance } from "react-test-renderer";
|
|
8
|
+
|
|
9
|
+
import { act, render, waitFor } from "@testing-library/react-native";
|
|
10
|
+
import { fireGestureHandler, getByGestureTestId } from "react-native-gesture-handler/jest-utils";
|
|
11
|
+
|
|
12
|
+
import Carousel from "./Carousel";
|
|
13
|
+
|
|
14
|
+
import type { TCarouselProps } from "../types";
|
|
15
|
+
|
|
16
|
+
// Suppress the "measure() cannot be used with Jest" warning from Reanimated.
|
|
17
|
+
// The measure export is non-configurable so it cannot be spied on or mocked
|
|
18
|
+
// at the module level. The code in ScrollViewGesture already handles the null
|
|
19
|
+
// return gracefully (measurement?.width || 0). We filter at the Reanimated
|
|
20
|
+
// logger level which is more reliable than intercepting console.warn.
|
|
21
|
+
{
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
+
const cfg = (global as any).__reanimatedLoggerConfig as
|
|
24
|
+
| { logFunction: (data: { level: number; message: string }) => void }
|
|
25
|
+
| undefined;
|
|
26
|
+
if (cfg) {
|
|
27
|
+
const _origLog = cfg.logFunction;
|
|
28
|
+
cfg.logFunction = (data) => {
|
|
29
|
+
if (data.message.includes("measure() cannot be used with Jest")) return;
|
|
30
|
+
_origLog(data);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
jest.setTimeout(1000 * 12);
|
|
36
|
+
|
|
37
|
+
const mockPan = jest.fn();
|
|
38
|
+
const realPan = Gesture.Pan();
|
|
39
|
+
const gestureTestId = "rnrc-gesture-handler";
|
|
40
|
+
|
|
41
|
+
jest.spyOn(Gesture, "Pan").mockImplementation(() => {
|
|
42
|
+
mockPan();
|
|
43
|
+
return realPan.withTestId(gestureTestId);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("Test the real swipe behavior of Carousel to ensure it's working as expected", () => {
|
|
47
|
+
const slideWidth = 300;
|
|
48
|
+
const slideHeight = 200;
|
|
49
|
+
const slideCount = 4;
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
mockPan.mockClear();
|
|
53
|
+
jest.useFakeTimers();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
act(() => {
|
|
58
|
+
jest.runOnlyPendingTimers();
|
|
59
|
+
});
|
|
60
|
+
jest.useRealTimers();
|
|
61
|
+
jest.clearAllTimers();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Helper function to create mock data
|
|
65
|
+
const createMockData = (length: number = slideCount) =>
|
|
66
|
+
Array.from({ length }, (_, i) => `Item ${i + 1}`);
|
|
67
|
+
|
|
68
|
+
// Helper function to create default props with correct typing
|
|
69
|
+
const createDefaultProps = (
|
|
70
|
+
progressAnimVal: SharedValue<number>,
|
|
71
|
+
customProps: Partial<TCarouselProps<string>> = {}
|
|
72
|
+
) => {
|
|
73
|
+
const baseProps: Partial<TCarouselProps<string>> = {
|
|
74
|
+
data: createMockData(),
|
|
75
|
+
defaultIndex: 0,
|
|
76
|
+
onProgressChange: (offsetProgress, absoluteProgress) => {
|
|
77
|
+
progressAnimVal.value = absoluteProgress;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...baseProps,
|
|
83
|
+
...customProps,
|
|
84
|
+
} as TCarouselProps<string>;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Helper function to create test wrapper
|
|
88
|
+
const createCarousel = (progress: { current: number }) => {
|
|
89
|
+
const Wrapper: FC<Partial<TCarouselProps<string>>> = React.forwardRef((customProps, ref) => {
|
|
90
|
+
const progressAnimVal = useSharedValue(progress.current);
|
|
91
|
+
const defaultRenderItem = ({
|
|
92
|
+
item,
|
|
93
|
+
index,
|
|
94
|
+
}: {
|
|
95
|
+
item: string;
|
|
96
|
+
index: number;
|
|
97
|
+
}) => (
|
|
98
|
+
<Animated.View
|
|
99
|
+
testID={`carousel-item-${index}`}
|
|
100
|
+
style={{ width: slideWidth, height: slideHeight, flex: 1 }}
|
|
101
|
+
>
|
|
102
|
+
{item}
|
|
103
|
+
</Animated.View>
|
|
104
|
+
);
|
|
105
|
+
const { renderItem = defaultRenderItem, ...defaultProps } = createDefaultProps(
|
|
106
|
+
progressAnimVal,
|
|
107
|
+
customProps
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
useDerivedValue(() => {
|
|
111
|
+
progress.current = progressAnimVal.value;
|
|
112
|
+
}, [progressAnimVal]);
|
|
113
|
+
|
|
114
|
+
return <Carousel {...defaultProps} renderItem={renderItem} ref={ref} />;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return Wrapper;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Helper function to simulate swipe
|
|
121
|
+
const swipeToLeftOnce = (
|
|
122
|
+
options: {
|
|
123
|
+
itemWidth?: number;
|
|
124
|
+
velocityX?: number;
|
|
125
|
+
} = {}
|
|
126
|
+
) => {
|
|
127
|
+
const { itemWidth = slideWidth, velocityX = -slideWidth } = options;
|
|
128
|
+
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
|
|
129
|
+
{ state: State.BEGAN, translationX: 0, velocityX },
|
|
130
|
+
{ state: State.ACTIVE, translationX: -itemWidth * 0.25, velocityX },
|
|
131
|
+
{ state: State.ACTIVE, translationX: -itemWidth * 0.5, velocityX },
|
|
132
|
+
{ state: State.ACTIVE, translationX: -itemWidth * 0.75, velocityX },
|
|
133
|
+
{ state: State.END, translationX: -itemWidth, velocityX },
|
|
134
|
+
]);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Helper function to verify initial render
|
|
138
|
+
const verifyInitialRender = async (
|
|
139
|
+
getByTestId: (testID: string | RegExp) => ReactTestInstance
|
|
140
|
+
) => {
|
|
141
|
+
await waitFor(
|
|
142
|
+
() => {
|
|
143
|
+
const item = getByTestId("carousel-item-0");
|
|
144
|
+
expect(item).toBeTruthy();
|
|
145
|
+
},
|
|
146
|
+
{ timeout: 1000 * 3 }
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
describe("TDD: Test upcoming refactoring for style props", () => {
|
|
151
|
+
beforeEach(() => {
|
|
152
|
+
jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
afterEach(() => {
|
|
156
|
+
(console.warn as jest.Mock).mockRestore();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("should show a deprecation warning when using the `width` prop", () => {
|
|
160
|
+
const progress = { current: 0 };
|
|
161
|
+
const Wrapper = createCarousel(progress);
|
|
162
|
+
render(<Wrapper width={300} style={{ height: 200 }} />);
|
|
163
|
+
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining("is deprecated"));
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("should take width from the new `style` prop", async () => {
|
|
167
|
+
const progress = { current: 0 };
|
|
168
|
+
const Wrapper = createCarousel(progress);
|
|
169
|
+
const { getByTestId } = render(
|
|
170
|
+
<Wrapper style={{ width: 450, height: 200 }} testID="carousel-container" />
|
|
171
|
+
);
|
|
172
|
+
await verifyInitialRender(getByTestId);
|
|
173
|
+
|
|
174
|
+
const outerContainer = getByTestId("carousel-container");
|
|
175
|
+
expect(outerContainer.props.style).toContainEqual({ width: 450, height: 200 });
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should apply styles from the new `contentContainerStyle` prop", async () => {
|
|
179
|
+
const progress = { current: 0 };
|
|
180
|
+
const Wrapper = createCarousel(progress);
|
|
181
|
+
const { getByTestId } = render(
|
|
182
|
+
<Wrapper
|
|
183
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
184
|
+
contentContainerStyle={{ padding: 20 }}
|
|
185
|
+
/>
|
|
186
|
+
);
|
|
187
|
+
await verifyInitialRender(getByTestId);
|
|
188
|
+
|
|
189
|
+
const contentContainer = getByTestId("carousel-content-container");
|
|
190
|
+
expect(contentContainer.props.style).toContainEqual({ padding: 20 });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should warn when `contentContainerStyle` contains conflicting props", async () => {
|
|
194
|
+
const progress = { current: 0 };
|
|
195
|
+
const Wrapper = createCarousel(progress);
|
|
196
|
+
const { getByTestId } = render(
|
|
197
|
+
<Wrapper
|
|
198
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
199
|
+
contentContainerStyle={{ opacity: 0.5 }}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
await verifyInitialRender(getByTestId);
|
|
203
|
+
|
|
204
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
205
|
+
expect.stringContaining("conflict with animations")
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should auto-size when no width is provided in `style`", async () => {
|
|
210
|
+
const progress = { current: 0 };
|
|
211
|
+
const Wrapper = createCarousel(progress);
|
|
212
|
+
const { getByTestId } = render(<Wrapper style={{ height: 200 }} />);
|
|
213
|
+
|
|
214
|
+
const contentContainer = getByTestId("carousel-content-container");
|
|
215
|
+
|
|
216
|
+
// Initially, width should be '100%'
|
|
217
|
+
expect(contentContainer.props.style[1].width).toBe("100%");
|
|
218
|
+
expect(typeof contentContainer.props.onLayout).toBe("function");
|
|
219
|
+
|
|
220
|
+
// Simulate onLayout event
|
|
221
|
+
act(() => {
|
|
222
|
+
contentContainer.props.onLayout?.({
|
|
223
|
+
nativeEvent: { layout: { width: 350, height: 200 } },
|
|
224
|
+
} as any);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
act(() => {
|
|
228
|
+
jest.runOnlyPendingTimers();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// No assertions on rendered items because reanimated mock does not process animated updates.
|
|
232
|
+
// Ensure invoking layout measurement does not throw and that the carousel exposes the expected
|
|
233
|
+
// measurement callback for auto-sizing scenarios.
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should use itemWidth for snapping size when provided", async () => {
|
|
237
|
+
const progress = { current: 0 };
|
|
238
|
+
const Wrapper = createCarousel(progress);
|
|
239
|
+
const containerWidth = 700;
|
|
240
|
+
const itemWidth = 350;
|
|
241
|
+
const { getByTestId } = render(
|
|
242
|
+
<Wrapper style={{ width: containerWidth, height: 200 }} itemWidth={itemWidth} />
|
|
243
|
+
);
|
|
244
|
+
await verifyInitialRender(getByTestId);
|
|
245
|
+
|
|
246
|
+
// The carousel should use itemWidth (350) for snapping instead of container width (700)
|
|
247
|
+
// Verify items render and content container is set up correctly
|
|
248
|
+
const contentContainer = getByTestId("carousel-content-container");
|
|
249
|
+
expect(contentContainer).toBeTruthy();
|
|
250
|
+
expect(getByTestId("carousel-item-0")).toBeTruthy();
|
|
251
|
+
expect(getByTestId("carousel-item-1")).toBeTruthy();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("should use itemHeight for snapping size in vertical mode when provided", async () => {
|
|
255
|
+
const progress = { current: 0 };
|
|
256
|
+
const Wrapper = createCarousel(progress);
|
|
257
|
+
const containerHeight = 700;
|
|
258
|
+
const itemHeight = 350;
|
|
259
|
+
const { getByTestId } = render(
|
|
260
|
+
<Wrapper vertical style={{ width: 350, height: containerHeight }} itemHeight={itemHeight} />
|
|
261
|
+
);
|
|
262
|
+
await verifyInitialRender(getByTestId);
|
|
263
|
+
|
|
264
|
+
// The carousel should use itemHeight (350) for snapping instead of container height (700)
|
|
265
|
+
// Verify items render - vertical mode uses the same snap logic
|
|
266
|
+
const contentContainer = getByTestId("carousel-content-container");
|
|
267
|
+
expect(contentContainer).toBeTruthy();
|
|
268
|
+
// Verify first item renders
|
|
269
|
+
expect(getByTestId("carousel-item-0")).toBeTruthy();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("should prioritize itemWidth over width prop", async () => {
|
|
273
|
+
const progress = { current: 0 };
|
|
274
|
+
const Wrapper = createCarousel(progress);
|
|
275
|
+
const containerWidth = 700;
|
|
276
|
+
const itemWidth = 350;
|
|
277
|
+
const { getByTestId } = render(
|
|
278
|
+
<Wrapper
|
|
279
|
+
style={{ width: containerWidth, height: 200 }}
|
|
280
|
+
width={containerWidth}
|
|
281
|
+
itemWidth={itemWidth}
|
|
282
|
+
/>
|
|
283
|
+
);
|
|
284
|
+
await verifyInitialRender(getByTestId);
|
|
285
|
+
|
|
286
|
+
// itemWidth (350) should take precedence over deprecated width prop
|
|
287
|
+
// Verify items render correctly with multiple visible
|
|
288
|
+
const contentContainer = getByTestId("carousel-content-container");
|
|
289
|
+
expect(contentContainer).toBeTruthy();
|
|
290
|
+
expect(getByTestId("carousel-item-0")).toBeTruthy();
|
|
291
|
+
expect(getByTestId("carousel-item-1")).toBeTruthy();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("should support itemWidth for multiple visible items scenario", async () => {
|
|
295
|
+
const progress = { current: 0 };
|
|
296
|
+
const Wrapper = createCarousel(progress);
|
|
297
|
+
const containerWidth = 900;
|
|
298
|
+
const itemWidth = 300;
|
|
299
|
+
const { getByTestId } = render(
|
|
300
|
+
<Wrapper
|
|
301
|
+
style={{ width: containerWidth, height: 200 }}
|
|
302
|
+
itemWidth={itemWidth}
|
|
303
|
+
data={createMockData(6)}
|
|
304
|
+
/>
|
|
305
|
+
);
|
|
306
|
+
await verifyInitialRender(getByTestId);
|
|
307
|
+
|
|
308
|
+
// Container is 900px, itemWidth is 300px, so 3 items should be visible
|
|
309
|
+
// Verify multiple items are rendered (visible in the viewport)
|
|
310
|
+
expect(getByTestId("carousel-item-0")).toBeTruthy();
|
|
311
|
+
expect(getByTestId("carousel-item-1")).toBeTruthy();
|
|
312
|
+
expect(getByTestId("carousel-item-2")).toBeTruthy();
|
|
313
|
+
expect(getByTestId("carousel-item-3")).toBeTruthy();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("should accept onLayout callback prop", async () => {
|
|
317
|
+
const progress = { current: 0 };
|
|
318
|
+
const onLayout = jest.fn();
|
|
319
|
+
const Wrapper = createCarousel(progress);
|
|
320
|
+
const { getByTestId } = render(
|
|
321
|
+
<Wrapper style={{ width: 700, height: 200 }} onLayout={onLayout} />
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
const contentContainer = getByTestId("carousel-content-container");
|
|
325
|
+
|
|
326
|
+
// Verify that onLayout handler is attached to the content container
|
|
327
|
+
expect(typeof contentContainer.props.onLayout).toBe("function");
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("`data` prop: should render correctly", async () => {
|
|
332
|
+
const progress = { current: 0 };
|
|
333
|
+
const Wrapper = createCarousel(progress);
|
|
334
|
+
const { getByTestId } = render(
|
|
335
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} data={createMockData(6)} />
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
await verifyInitialRender(getByTestId);
|
|
339
|
+
|
|
340
|
+
expect(getByTestId("carousel-item-0")).toBeTruthy();
|
|
341
|
+
expect(getByTestId("carousel-item-1")).toBeTruthy();
|
|
342
|
+
expect(getByTestId("carousel-item-2")).toBeTruthy();
|
|
343
|
+
expect(getByTestId("carousel-item-3")).toBeTruthy();
|
|
344
|
+
expect(getByTestId("carousel-item-4")).toBeTruthy();
|
|
345
|
+
expect(getByTestId("carousel-item-5")).toBeTruthy();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("`renderItem` prop: should render items correctly", async () => {
|
|
349
|
+
const progress = { current: 0 };
|
|
350
|
+
const Wrapper = createCarousel(progress);
|
|
351
|
+
const { getByTestId } = render(
|
|
352
|
+
<Wrapper
|
|
353
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
354
|
+
renderItem={({ item, index }) => (
|
|
355
|
+
<Animated.Text testID={`item-${index}`}>{item}</Animated.Text>
|
|
356
|
+
)}
|
|
357
|
+
/>
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
await waitFor(() => expect(getByTestId("item-0")).toBeTruthy());
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("should swipe to the left", async () => {
|
|
364
|
+
const progress = { current: 0 };
|
|
365
|
+
const Wrapper = createCarousel(progress);
|
|
366
|
+
const { getByTestId } = render(<Wrapper style={{ width: slideWidth, height: slideHeight }} />);
|
|
367
|
+
await verifyInitialRender(getByTestId);
|
|
368
|
+
|
|
369
|
+
// Test swipe sequence
|
|
370
|
+
for (let i = 1; i <= slideCount; i++) {
|
|
371
|
+
swipeToLeftOnce();
|
|
372
|
+
await waitFor(() => expect(progress.current).toBe(i % slideCount));
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("`loop` prop: should swipe back to the first item when loop is true", async () => {
|
|
377
|
+
const progress = { current: 0 };
|
|
378
|
+
const Wrapper = createCarousel(progress);
|
|
379
|
+
{
|
|
380
|
+
const { getByTestId } = render(
|
|
381
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} loop />
|
|
382
|
+
);
|
|
383
|
+
await verifyInitialRender(getByTestId);
|
|
384
|
+
|
|
385
|
+
// Test swipe sequence
|
|
386
|
+
for (let i = 1; i <= slideCount; i++) {
|
|
387
|
+
swipeToLeftOnce();
|
|
388
|
+
await waitFor(() => expect(progress.current).toBe(i % slideCount));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
{
|
|
393
|
+
const { getByTestId } = render(
|
|
394
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} loop={false} />
|
|
395
|
+
);
|
|
396
|
+
await verifyInitialRender(getByTestId);
|
|
397
|
+
|
|
398
|
+
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
|
|
399
|
+
{ state: State.BEGAN, translationX: 0 },
|
|
400
|
+
{ state: State.ACTIVE, translationX: slideWidth * 0.25 },
|
|
401
|
+
{ state: State.END, translationX: slideWidth * 0.5 },
|
|
402
|
+
]);
|
|
403
|
+
|
|
404
|
+
// Because the loop is false, so the the carousel will swipe back to the first item
|
|
405
|
+
await waitFor(() => expect(progress.current).toBe(0));
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("`onSnapToItem` prop: should call the onSnapToItem callback", async () => {
|
|
410
|
+
const progress = { current: 0 };
|
|
411
|
+
const onSnapToItem = jest.fn();
|
|
412
|
+
const Wrapper = createCarousel(progress);
|
|
413
|
+
const { getByTestId } = render(
|
|
414
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} onSnapToItem={onSnapToItem} />
|
|
415
|
+
);
|
|
416
|
+
await verifyInitialRender(getByTestId);
|
|
417
|
+
expect(onSnapToItem).not.toHaveBeenCalled();
|
|
418
|
+
|
|
419
|
+
swipeToLeftOnce();
|
|
420
|
+
await waitFor(() => expect(onSnapToItem).toHaveBeenCalledWith(1));
|
|
421
|
+
|
|
422
|
+
swipeToLeftOnce();
|
|
423
|
+
await waitFor(() => expect(onSnapToItem).toHaveBeenCalledWith(2));
|
|
424
|
+
|
|
425
|
+
swipeToLeftOnce();
|
|
426
|
+
await waitFor(() => expect(onSnapToItem).toHaveBeenCalledWith(3));
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it("`autoPlay` prop: should swipe automatically when autoPlay is true", async () => {
|
|
430
|
+
const progress = { current: 0 };
|
|
431
|
+
const Wrapper = createCarousel(progress);
|
|
432
|
+
const { getByTestId } = render(
|
|
433
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} autoPlay autoPlayInterval={300} />
|
|
434
|
+
);
|
|
435
|
+
await verifyInitialRender(getByTestId);
|
|
436
|
+
|
|
437
|
+
await waitFor(() => expect(progress.current).toBe(1));
|
|
438
|
+
await waitFor(() => expect(progress.current).toBe(2));
|
|
439
|
+
await waitFor(() => expect(progress.current).toBe(3));
|
|
440
|
+
await waitFor(() => expect(progress.current).toBe(0));
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it("`autoPlayReverse` prop: should swipe automatically in reverse when autoPlayReverse is true", async () => {
|
|
444
|
+
const progress = { current: 0 };
|
|
445
|
+
const Wrapper = createCarousel(progress);
|
|
446
|
+
|
|
447
|
+
render(
|
|
448
|
+
<Wrapper
|
|
449
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
450
|
+
autoPlay
|
|
451
|
+
autoPlayReverse
|
|
452
|
+
autoPlayInterval={300}
|
|
453
|
+
scrollAnimationDuration={250}
|
|
454
|
+
/>
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
const step = (expectedIndex: number) => {
|
|
458
|
+
act(() => {
|
|
459
|
+
jest.advanceTimersByTime(300 + 250 + 1);
|
|
460
|
+
});
|
|
461
|
+
expect(Math.round(((progress.current % 4) + 4) % 4)).toBe(expectedIndex);
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
step(3);
|
|
465
|
+
step(2);
|
|
466
|
+
step(1);
|
|
467
|
+
step(0);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it("`defaultIndex` prop: should render the correct item with the defaultIndex props", async () => {
|
|
471
|
+
const progress = { current: 0 };
|
|
472
|
+
const Wrapper = createCarousel(progress);
|
|
473
|
+
const { getByTestId } = render(
|
|
474
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} defaultIndex={2} />
|
|
475
|
+
);
|
|
476
|
+
await verifyInitialRender(getByTestId);
|
|
477
|
+
|
|
478
|
+
await waitFor(() => expect(progress.current).toBe(2));
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("`defaultScrollOffsetValue` prop: should render the correct progress value with the defaultScrollOffsetValue props", async () => {
|
|
482
|
+
const progress = { current: 0 };
|
|
483
|
+
const Wrapper = createCarousel(progress);
|
|
484
|
+
const WrapperWithCustomProps = () => {
|
|
485
|
+
const defaultScrollOffsetValue = useSharedValue(-slideWidth);
|
|
486
|
+
|
|
487
|
+
return (
|
|
488
|
+
<Wrapper
|
|
489
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
490
|
+
defaultScrollOffsetValue={defaultScrollOffsetValue}
|
|
491
|
+
/>
|
|
492
|
+
);
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
render(<WrapperWithCustomProps />);
|
|
496
|
+
|
|
497
|
+
await waitFor(() => expect(progress.current).toBe(1));
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it("`ref` prop: should handle the ref props", async () => {
|
|
501
|
+
const Wrapper = createCarousel({ current: 0 });
|
|
502
|
+
const fn = jest.fn();
|
|
503
|
+
const WrapperWithCustomProps: FC<{
|
|
504
|
+
refSetupCallback: (ref: boolean) => void;
|
|
505
|
+
}> = ({ refSetupCallback }) => {
|
|
506
|
+
return (
|
|
507
|
+
<Wrapper
|
|
508
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
509
|
+
ref={(ref) => {
|
|
510
|
+
refSetupCallback(!!ref);
|
|
511
|
+
}}
|
|
512
|
+
/>
|
|
513
|
+
);
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
render(<WrapperWithCustomProps refSetupCallback={fn} />);
|
|
517
|
+
|
|
518
|
+
await waitFor(() => expect(fn).toHaveBeenCalledWith(true));
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it("`autoFillData` prop: should auto fill data array to allow loop playback when the loop props is true", async () => {
|
|
522
|
+
const progress = { current: 0 };
|
|
523
|
+
const Wrapper = createCarousel(progress);
|
|
524
|
+
{
|
|
525
|
+
const { getAllByTestId } = render(
|
|
526
|
+
<Wrapper
|
|
527
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
528
|
+
autoFillData
|
|
529
|
+
data={createMockData(1)}
|
|
530
|
+
/>
|
|
531
|
+
);
|
|
532
|
+
await waitFor(() => {
|
|
533
|
+
expect(getAllByTestId("carousel-item-0").length).toBe(3);
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
{
|
|
538
|
+
const { getAllByTestId } = render(
|
|
539
|
+
<Wrapper
|
|
540
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
541
|
+
autoFillData={false}
|
|
542
|
+
data={createMockData(1)}
|
|
543
|
+
/>
|
|
544
|
+
);
|
|
545
|
+
await waitFor(() => {
|
|
546
|
+
expect(getAllByTestId("carousel-item-0").length).toBe(1);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it("`pagingEnabled` prop: should swipe to the next item when pagingEnabled is true", async () => {
|
|
552
|
+
const progress = { current: 0 };
|
|
553
|
+
const Wrapper = createCarousel(progress);
|
|
554
|
+
{
|
|
555
|
+
const { getByTestId } = render(
|
|
556
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} pagingEnabled={false} />
|
|
557
|
+
);
|
|
558
|
+
await verifyInitialRender(getByTestId);
|
|
559
|
+
|
|
560
|
+
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
|
|
561
|
+
{ state: State.BEGAN, translationX: 0, velocityX: -5 },
|
|
562
|
+
{
|
|
563
|
+
state: State.ACTIVE,
|
|
564
|
+
translationX: -slideWidth * 0.15,
|
|
565
|
+
velocityX: -5,
|
|
566
|
+
},
|
|
567
|
+
{ state: State.END, translationX: -slideWidth * 0.25, velocityX: -5 },
|
|
568
|
+
]);
|
|
569
|
+
|
|
570
|
+
await waitFor(() => expect(progress.current).toBe(0));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
{
|
|
574
|
+
const { getByTestId } = render(
|
|
575
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} pagingEnabled />
|
|
576
|
+
);
|
|
577
|
+
await verifyInitialRender(getByTestId);
|
|
578
|
+
|
|
579
|
+
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
|
|
580
|
+
{ state: State.BEGAN, translationX: 0, velocityX: -1000 },
|
|
581
|
+
{
|
|
582
|
+
state: State.ACTIVE,
|
|
583
|
+
translationX: -slideWidth * 0.15,
|
|
584
|
+
velocityX: -1000,
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
state: State.END,
|
|
588
|
+
translationX: -slideWidth * 0.25,
|
|
589
|
+
velocityX: -1000,
|
|
590
|
+
},
|
|
591
|
+
]);
|
|
592
|
+
|
|
593
|
+
await waitFor(() => expect(progress.current).toBe(1));
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it("`onConfigurePanGesture` prop: should call the onConfigurePanGesture callback", async () => {
|
|
598
|
+
const progress = { current: 0 };
|
|
599
|
+
const Wrapper = createCarousel(progress);
|
|
600
|
+
let _pan: PanGesture | null = null;
|
|
601
|
+
render(
|
|
602
|
+
<Wrapper
|
|
603
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
604
|
+
onConfigurePanGesture={(pan) => {
|
|
605
|
+
_pan = pan;
|
|
606
|
+
return pan;
|
|
607
|
+
}}
|
|
608
|
+
/>
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
const { getByTestId } = render(
|
|
612
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} pagingEnabled={false} />
|
|
613
|
+
);
|
|
614
|
+
await verifyInitialRender(getByTestId);
|
|
615
|
+
expect(_pan).not.toBeNull();
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it("`onScrollStart` prop: should call the onScrollStart callback", async () => {
|
|
619
|
+
const progress = { current: 0 };
|
|
620
|
+
let startedProgress: number | undefined;
|
|
621
|
+
const onScrollStart = () => {
|
|
622
|
+
if (typeof startedProgress === "number") return;
|
|
623
|
+
|
|
624
|
+
startedProgress = progress.current;
|
|
625
|
+
};
|
|
626
|
+
const Wrapper = createCarousel(progress);
|
|
627
|
+
const { getByTestId } = render(
|
|
628
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} onScrollStart={onScrollStart} />
|
|
629
|
+
);
|
|
630
|
+
await verifyInitialRender(getByTestId);
|
|
631
|
+
|
|
632
|
+
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
|
|
633
|
+
{ state: State.BEGAN, translationX: 0, velocityX: 1000 },
|
|
634
|
+
{ state: State.ACTIVE, translationX: slideWidth / 2, velocityX: 1000 },
|
|
635
|
+
{ state: State.END, translationX: slideWidth, velocityX: 1000 },
|
|
636
|
+
]);
|
|
637
|
+
|
|
638
|
+
await waitFor(() => {
|
|
639
|
+
expect(startedProgress).toBe(0);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
it("`onScrollEnd` prop: should call the onScrollEnd callback", async () => {
|
|
644
|
+
const progress = { current: 0 };
|
|
645
|
+
let endedProgress: number | undefined;
|
|
646
|
+
const onScrollEnd = jest.fn(() => {
|
|
647
|
+
if (typeof endedProgress === "number") return;
|
|
648
|
+
|
|
649
|
+
endedProgress = progress.current;
|
|
650
|
+
});
|
|
651
|
+
const Wrapper = createCarousel(progress);
|
|
652
|
+
const { getByTestId } = render(
|
|
653
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} onScrollEnd={onScrollEnd} />
|
|
654
|
+
);
|
|
655
|
+
await verifyInitialRender(getByTestId);
|
|
656
|
+
|
|
657
|
+
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
|
|
658
|
+
{ state: State.BEGAN, translationX: 0, velocityX: 1000 },
|
|
659
|
+
{ state: State.ACTIVE, translationX: slideWidth / 2, velocityX: 1000 },
|
|
660
|
+
{ state: State.END, translationX: slideWidth, velocityX: 1000 },
|
|
661
|
+
]);
|
|
662
|
+
|
|
663
|
+
await waitFor(() => {
|
|
664
|
+
expect(endedProgress).toBe(3);
|
|
665
|
+
expect(onScrollEnd).toHaveBeenCalledWith(3);
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it("`onProgressChange` prop: should call the onProgressChange callback", async () => {
|
|
670
|
+
const offsetProgressVal = { current: 0 };
|
|
671
|
+
const absoluteProgressVal = { current: 0 };
|
|
672
|
+
const onProgressChange = jest.fn((offsetProgress, absoluteProgress) => {
|
|
673
|
+
offsetProgressVal.current = offsetProgress;
|
|
674
|
+
absoluteProgressVal.current = absoluteProgress;
|
|
675
|
+
});
|
|
676
|
+
const Wrapper = createCarousel(offsetProgressVal);
|
|
677
|
+
const { getByTestId } = render(
|
|
678
|
+
<Wrapper
|
|
679
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
680
|
+
onProgressChange={onProgressChange}
|
|
681
|
+
defaultIndex={0}
|
|
682
|
+
/>
|
|
683
|
+
);
|
|
684
|
+
await verifyInitialRender(getByTestId);
|
|
685
|
+
|
|
686
|
+
await waitFor(() => {
|
|
687
|
+
expect(offsetProgressVal.current).toBe(0);
|
|
688
|
+
expect(absoluteProgressVal.current).toBe(0);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
|
|
692
|
+
{ state: State.BEGAN, translationX: 0, velocityX: -1000 },
|
|
693
|
+
{ state: State.ACTIVE, translationX: -slideWidth / 2, velocityX: -1000 },
|
|
694
|
+
{ state: State.END, translationX: -slideWidth, velocityX: -1000 },
|
|
695
|
+
]);
|
|
696
|
+
|
|
697
|
+
await waitFor(() => {
|
|
698
|
+
expect(offsetProgressVal.current).toBe(-slideWidth);
|
|
699
|
+
expect(absoluteProgressVal.current).toBe(1);
|
|
700
|
+
});
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it("`fixedDirection` prop: should swipe to the correct direction when fixedDirection is positive", async () => {
|
|
704
|
+
{
|
|
705
|
+
const progress = { current: 0 };
|
|
706
|
+
const Wrapper = createCarousel(progress);
|
|
707
|
+
const { getByTestId } = render(
|
|
708
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} fixedDirection="positive" />
|
|
709
|
+
);
|
|
710
|
+
await verifyInitialRender(getByTestId);
|
|
711
|
+
|
|
712
|
+
swipeToLeftOnce({ velocityX: slideWidth });
|
|
713
|
+
await waitFor(() => {
|
|
714
|
+
expect(progress.current).toBe(3);
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
{
|
|
719
|
+
const progress = { current: 0 };
|
|
720
|
+
const Wrapper = createCarousel(progress);
|
|
721
|
+
const { getByTestId } = render(
|
|
722
|
+
<Wrapper style={{ width: slideWidth, height: slideHeight }} fixedDirection="negative" />
|
|
723
|
+
);
|
|
724
|
+
await verifyInitialRender(getByTestId);
|
|
725
|
+
|
|
726
|
+
swipeToLeftOnce({ velocityX: -slideWidth });
|
|
727
|
+
await waitFor(() => expect(progress.current).toBe(1));
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
it("`customAnimation` prop: should apply the custom animation", async () => {
|
|
732
|
+
const progress = { current: 0 };
|
|
733
|
+
const indexes: Record<number, number> = {};
|
|
734
|
+
const Wrapper = createCarousel(progress);
|
|
735
|
+
const { getByTestId } = render(
|
|
736
|
+
<Wrapper
|
|
737
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
738
|
+
customAnimation={(value: number, index: number) => {
|
|
739
|
+
"worklet";
|
|
740
|
+
|
|
741
|
+
indexes[index] = index;
|
|
742
|
+
|
|
743
|
+
const zIndex = interpolate(value, [-1, 0, 1], [10, 20, 30]);
|
|
744
|
+
const translateX = interpolate(value, [-2, 0, 1], [-slideWidth, 0, slideWidth]);
|
|
745
|
+
|
|
746
|
+
return {
|
|
747
|
+
transform: [{ translateX }],
|
|
748
|
+
zIndex,
|
|
749
|
+
};
|
|
750
|
+
}}
|
|
751
|
+
/>
|
|
752
|
+
);
|
|
753
|
+
|
|
754
|
+
await verifyInitialRender(getByTestId);
|
|
755
|
+
|
|
756
|
+
swipeToLeftOnce();
|
|
757
|
+
await waitFor(() => {
|
|
758
|
+
expect(progress.current).toBe(1);
|
|
759
|
+
|
|
760
|
+
expect(indexes).toMatchInlineSnapshot(`
|
|
761
|
+
{
|
|
762
|
+
"0": 0,
|
|
763
|
+
"1": 1,
|
|
764
|
+
"2": 2,
|
|
765
|
+
"3": 3,
|
|
766
|
+
}
|
|
767
|
+
`);
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
it("`overscrollEnabled` prop: should respect overscrollEnabled=false and prevent scrolling beyond bounds", async () => {
|
|
772
|
+
const containerWidth = slideWidth;
|
|
773
|
+
const containerHeight = containerWidth / 2;
|
|
774
|
+
|
|
775
|
+
let nextSlide: (() => void) | undefined;
|
|
776
|
+
const testId = "CarouselAnimatedView";
|
|
777
|
+
const progress = { current: 0 };
|
|
778
|
+
const CarouselW = createCarousel(progress);
|
|
779
|
+
|
|
780
|
+
const SCROLL_MS = 250; // Make the animation duration controllable
|
|
781
|
+
const TICK = (ms = SCROLL_MS + 10) =>
|
|
782
|
+
act(() => {
|
|
783
|
+
jest.advanceTimersByTime(ms);
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
const { getByTestId } = render(
|
|
787
|
+
<CarouselW
|
|
788
|
+
ref={(ref) => {
|
|
789
|
+
if (ref) nextSlide = ref.next;
|
|
790
|
+
}}
|
|
791
|
+
vertical={false}
|
|
792
|
+
style={{ width: containerWidth, height: containerHeight }}
|
|
793
|
+
testID={testId}
|
|
794
|
+
loop={false}
|
|
795
|
+
overscrollEnabled={false}
|
|
796
|
+
data={createMockData(6)}
|
|
797
|
+
pagingEnabled={false}
|
|
798
|
+
scrollAnimationDuration={SCROLL_MS}
|
|
799
|
+
/>
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
// Simulate layout
|
|
803
|
+
act(() => {
|
|
804
|
+
getByTestId("carousel-content-container").props.onLayout({
|
|
805
|
+
nativeEvent: { layout: { width: containerWidth, height: containerHeight } },
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// Let the internal async initialization run
|
|
810
|
+
TICK(1);
|
|
811
|
+
|
|
812
|
+
const getProgress = () =>
|
|
813
|
+
Math.round(((progress.current % slideCount) + slideCount) % slideCount);
|
|
814
|
+
const captured: number[] = [];
|
|
815
|
+
const pushExpect = (expected: number) => {
|
|
816
|
+
captured.push(getProgress());
|
|
817
|
+
expect(captured[captured.length - 1]).toBe(expected);
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// Initial: At the 0th page
|
|
821
|
+
pushExpect(0);
|
|
822
|
+
|
|
823
|
+
// next -> 1st page
|
|
824
|
+
nextSlide?.();
|
|
825
|
+
TICK(); // Wait for the animation to end
|
|
826
|
+
pushExpect(1);
|
|
827
|
+
|
|
828
|
+
// next -> 2nd page
|
|
829
|
+
nextSlide?.();
|
|
830
|
+
TICK();
|
|
831
|
+
pushExpect(2);
|
|
832
|
+
|
|
833
|
+
// next -> 3rd page (still allowed; still enough content)
|
|
834
|
+
nextSlide?.();
|
|
835
|
+
TICK();
|
|
836
|
+
pushExpect(3);
|
|
837
|
+
|
|
838
|
+
// next -> 4th page
|
|
839
|
+
nextSlide?.();
|
|
840
|
+
TICK();
|
|
841
|
+
pushExpect(0);
|
|
842
|
+
|
|
843
|
+
// next -> 5th page (last item)
|
|
844
|
+
nextSlide?.();
|
|
845
|
+
TICK();
|
|
846
|
+
pushExpect(1);
|
|
847
|
+
|
|
848
|
+
// continue next(Already at the last page, and overscroll=false, should not move)
|
|
849
|
+
nextSlide?.();
|
|
850
|
+
TICK();
|
|
851
|
+
pushExpect(1);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
it("`overscrollEnabled` false should clamp right overdrag at the first page in non-loop mode", async () => {
|
|
855
|
+
const handlerOffset = { current: 0 };
|
|
856
|
+
const maxObservedPositiveOffset = { current: 0 };
|
|
857
|
+
|
|
858
|
+
const Wrapper: FC<Partial<TCarouselProps<string>>> = React.forwardRef((customProps, ref) => {
|
|
859
|
+
const progressAnimVal = useSharedValue(0);
|
|
860
|
+
const mockHandlerOffset = useSharedValue(handlerOffset.current);
|
|
861
|
+
const defaultRenderItem = ({
|
|
862
|
+
item,
|
|
863
|
+
index,
|
|
864
|
+
}: {
|
|
865
|
+
item: string;
|
|
866
|
+
index: number;
|
|
867
|
+
}) => (
|
|
868
|
+
<Animated.View
|
|
869
|
+
testID={`carousel-item-${index}`}
|
|
870
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
871
|
+
>
|
|
872
|
+
{item}
|
|
873
|
+
</Animated.View>
|
|
874
|
+
);
|
|
875
|
+
const { renderItem = defaultRenderItem, ...defaultProps } = createDefaultProps(
|
|
876
|
+
progressAnimVal,
|
|
877
|
+
customProps
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
useDerivedValue(() => {
|
|
881
|
+
handlerOffset.current = mockHandlerOffset.value;
|
|
882
|
+
maxObservedPositiveOffset.current = Math.max(
|
|
883
|
+
maxObservedPositiveOffset.current,
|
|
884
|
+
mockHandlerOffset.value
|
|
885
|
+
);
|
|
886
|
+
}, [mockHandlerOffset]);
|
|
887
|
+
|
|
888
|
+
return (
|
|
889
|
+
<Carousel
|
|
890
|
+
{...defaultProps}
|
|
891
|
+
defaultScrollOffsetValue={mockHandlerOffset}
|
|
892
|
+
renderItem={renderItem}
|
|
893
|
+
ref={ref}
|
|
894
|
+
/>
|
|
895
|
+
);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
const { getByTestId } = render(
|
|
899
|
+
<Wrapper
|
|
900
|
+
loop={false}
|
|
901
|
+
overscrollEnabled={false}
|
|
902
|
+
pagingEnabled={false}
|
|
903
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
904
|
+
/>
|
|
905
|
+
);
|
|
906
|
+
await verifyInitialRender(getByTestId);
|
|
907
|
+
|
|
908
|
+
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
|
|
909
|
+
{ state: State.BEGAN, translationX: 0, velocityX: slideWidth },
|
|
910
|
+
]);
|
|
911
|
+
|
|
912
|
+
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
|
|
913
|
+
{ state: State.ACTIVE, translationX: slideWidth * 0.6, velocityX: slideWidth },
|
|
914
|
+
]);
|
|
915
|
+
|
|
916
|
+
await waitFor(() => {
|
|
917
|
+
expect(maxObservedPositiveOffset.current).toBe(0);
|
|
918
|
+
});
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
it("should keep correct page after left overscroll at first page when calling next() or scrollTo()", async () => {
|
|
922
|
+
const handlerOffset = { current: 0 };
|
|
923
|
+
let nextSlide: ((opts?: { animated?: boolean }) => void) | undefined;
|
|
924
|
+
let scrollToIndex: ((opts?: { index: number; animated?: boolean }) => void) | undefined;
|
|
925
|
+
|
|
926
|
+
const Wrapper: FC<Partial<TCarouselProps<string>>> = React.forwardRef((customProps, ref) => {
|
|
927
|
+
const progressAnimVal = useSharedValue(0);
|
|
928
|
+
const mockHandlerOffset = useSharedValue(handlerOffset.current);
|
|
929
|
+
const defaultRenderItem = ({
|
|
930
|
+
item,
|
|
931
|
+
index,
|
|
932
|
+
}: {
|
|
933
|
+
item: string;
|
|
934
|
+
index: number;
|
|
935
|
+
}) => (
|
|
936
|
+
<Animated.View
|
|
937
|
+
testID={`carousel-item-${index}`}
|
|
938
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
939
|
+
>
|
|
940
|
+
{item}
|
|
941
|
+
</Animated.View>
|
|
942
|
+
);
|
|
943
|
+
const { renderItem = defaultRenderItem, ...defaultProps } = createDefaultProps(
|
|
944
|
+
progressAnimVal,
|
|
945
|
+
customProps
|
|
946
|
+
);
|
|
947
|
+
|
|
948
|
+
useDerivedValue(() => {
|
|
949
|
+
handlerOffset.current = mockHandlerOffset.value;
|
|
950
|
+
}, [mockHandlerOffset]);
|
|
951
|
+
|
|
952
|
+
return (
|
|
953
|
+
<Carousel
|
|
954
|
+
{...defaultProps}
|
|
955
|
+
defaultScrollOffsetValue={mockHandlerOffset}
|
|
956
|
+
renderItem={renderItem}
|
|
957
|
+
ref={ref}
|
|
958
|
+
/>
|
|
959
|
+
);
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
const { getByTestId } = render(
|
|
963
|
+
<Wrapper
|
|
964
|
+
ref={(ref) => {
|
|
965
|
+
if (ref) {
|
|
966
|
+
nextSlide = ref.next;
|
|
967
|
+
scrollToIndex = ref.scrollTo;
|
|
968
|
+
}
|
|
969
|
+
}}
|
|
970
|
+
loop={false}
|
|
971
|
+
overscrollEnabled
|
|
972
|
+
style={{ width: slideWidth, height: slideHeight }}
|
|
973
|
+
/>
|
|
974
|
+
);
|
|
975
|
+
await verifyInitialRender(getByTestId);
|
|
976
|
+
|
|
977
|
+
// Simulate left overscroll at first page
|
|
978
|
+
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
|
|
979
|
+
{ state: State.BEGAN, translationX: 0, velocityX: 0 },
|
|
980
|
+
{ state: State.ACTIVE, translationX: slideWidth / 4, velocityX: slideWidth },
|
|
981
|
+
{ state: State.ACTIVE, translationX: 0.00003996, velocityX: slideWidth },
|
|
982
|
+
{ state: State.END, translationX: 0.00003996, velocityX: slideWidth },
|
|
983
|
+
]);
|
|
984
|
+
|
|
985
|
+
nextSlide?.({ animated: false });
|
|
986
|
+
await waitFor(() => {
|
|
987
|
+
expect(handlerOffset.current).toBe(-slideWidth);
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
// Overscroll again, then call scrollTo()
|
|
991
|
+
fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
|
|
992
|
+
{ state: State.BEGAN, translationX: 0, velocityX: -slideWidth },
|
|
993
|
+
{ state: State.ACTIVE, translationX: slideWidth, velocityX: slideWidth },
|
|
994
|
+
{ state: State.ACTIVE, translationX: slideWidth + 0.00003996, velocityX: slideWidth },
|
|
995
|
+
{ state: State.END, translationX: slideWidth + 0.00003996, velocityX: slideWidth },
|
|
996
|
+
]);
|
|
997
|
+
|
|
998
|
+
scrollToIndex?.({ index: 1, animated: false });
|
|
999
|
+
await waitFor(() => {
|
|
1000
|
+
expect(handlerOffset.current).toBe(-slideWidth);
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
describe("Carousel sizing and measurement", () => {
|
|
1005
|
+
it("should render items even before onLayout provides size (flex-based sizing)", async () => {
|
|
1006
|
+
const progress = { current: 0 };
|
|
1007
|
+
const Wrapper = createCarousel(progress);
|
|
1008
|
+
|
|
1009
|
+
// Render with flex: 1 (no explicit width/height values)
|
|
1010
|
+
const { queryByTestId } = render(<Wrapper style={{ flex: 1, height: 200 }} />);
|
|
1011
|
+
|
|
1012
|
+
// Items should render immediately with initial visible ranges
|
|
1013
|
+
// even if size measurement hasn't completed yet
|
|
1014
|
+
await waitFor(
|
|
1015
|
+
() => {
|
|
1016
|
+
const item = queryByTestId("carousel-item-0");
|
|
1017
|
+
expect(item).toBeTruthy();
|
|
1018
|
+
},
|
|
1019
|
+
{ timeout: 1000 * 3 }
|
|
1020
|
+
);
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
it("should render items with explicit style dimensions", async () => {
|
|
1024
|
+
const progress = { current: 0 };
|
|
1025
|
+
const Wrapper = createCarousel(progress);
|
|
1026
|
+
|
|
1027
|
+
const { queryByTestId } = render(<Wrapper style={{ width: 400, height: 250 }} />);
|
|
1028
|
+
|
|
1029
|
+
// Items should render with explicit dimensions
|
|
1030
|
+
await waitFor(
|
|
1031
|
+
() => {
|
|
1032
|
+
const item = queryByTestId("carousel-item-0");
|
|
1033
|
+
expect(item).toBeTruthy();
|
|
1034
|
+
},
|
|
1035
|
+
{ timeout: 1000 * 3 }
|
|
1036
|
+
);
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
it("should render items with itemWidth for custom snap distance", async () => {
|
|
1040
|
+
const progress = { current: 0 };
|
|
1041
|
+
const Wrapper = createCarousel(progress);
|
|
1042
|
+
const containerWidth = 600;
|
|
1043
|
+
const itemWidth = 200; // 3 items visible
|
|
1044
|
+
|
|
1045
|
+
const { getByTestId } = render(
|
|
1046
|
+
<Wrapper style={{ width: containerWidth, height: 200 }} itemWidth={itemWidth} />
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
// Items should render with itemWidth configuration
|
|
1050
|
+
await waitFor(
|
|
1051
|
+
() => {
|
|
1052
|
+
const item = getByTestId("carousel-item-0");
|
|
1053
|
+
expect(item).toBeTruthy();
|
|
1054
|
+
},
|
|
1055
|
+
{ timeout: 1000 * 3 }
|
|
1056
|
+
);
|
|
1057
|
+
|
|
1058
|
+
// Verify multiple items are visible due to smaller itemWidth
|
|
1059
|
+
expect(getByTestId("carousel-item-1")).toBeTruthy();
|
|
1060
|
+
expect(getByTestId("carousel-item-2")).toBeTruthy();
|
|
1061
|
+
});
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
describe("variable-size mode (getItemWidth / getItemHeight)", () => {
|
|
1065
|
+
const variableWidths = [80, 150, 220, 100, 300];
|
|
1066
|
+
|
|
1067
|
+
it("renders all visible items synchronously with declared widths", async () => {
|
|
1068
|
+
const progress = { current: 0 };
|
|
1069
|
+
const Wrapper = createCarousel(progress);
|
|
1070
|
+
const getItemWidth = (i: number) => variableWidths[i];
|
|
1071
|
+
|
|
1072
|
+
const { getByTestId } = render(
|
|
1073
|
+
<Wrapper
|
|
1074
|
+
data={variableWidths.map((_, i) => `item-${i}`)}
|
|
1075
|
+
style={{ width: 600, height: 200 }}
|
|
1076
|
+
getItemWidth={getItemWidth}
|
|
1077
|
+
loop={false}
|
|
1078
|
+
windowSize={5}
|
|
1079
|
+
/>
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1082
|
+
await verifyInitialRender(getByTestId);
|
|
1083
|
+
// The whole window should be in the DOM tree on first paint — this is
|
|
1084
|
+
// the "no blanks" invariant the variable-size mode preserves.
|
|
1085
|
+
for (let i = 0; i < variableWidths.length; i++) {
|
|
1086
|
+
expect(getByTestId(`carousel-item-${i}`)).toBeTruthy();
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
it("scrollTo({ index }) lands on the requested item", async () => {
|
|
1091
|
+
const progress = { current: 0 };
|
|
1092
|
+
const Wrapper = createCarousel(progress);
|
|
1093
|
+
const getItemWidth = (i: number) => variableWidths[i];
|
|
1094
|
+
const onSnap = jest.fn();
|
|
1095
|
+
const ref = React.createRef<import("../types").ICarouselInstance>();
|
|
1096
|
+
|
|
1097
|
+
render(
|
|
1098
|
+
<Wrapper
|
|
1099
|
+
ref={ref}
|
|
1100
|
+
data={variableWidths.map((_, i) => `item-${i}`)}
|
|
1101
|
+
style={{ width: 600, height: 200 }}
|
|
1102
|
+
getItemWidth={getItemWidth}
|
|
1103
|
+
loop={false}
|
|
1104
|
+
onSnapToItem={onSnap}
|
|
1105
|
+
/>
|
|
1106
|
+
);
|
|
1107
|
+
|
|
1108
|
+
await waitFor(() => expect(ref.current).toBeTruthy(), { timeout: 1000 * 3 });
|
|
1109
|
+
|
|
1110
|
+
act(() => {
|
|
1111
|
+
ref.current!.scrollTo({ index: 3, animated: false });
|
|
1112
|
+
});
|
|
1113
|
+
// Run pending animation frames so the imperative scroll settles.
|
|
1114
|
+
act(() => {
|
|
1115
|
+
jest.runOnlyPendingTimers();
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
// getCurrentIndex reads from the animated index value, which is updated
|
|
1119
|
+
// by the useAnimatedReaction in useCarouselController.
|
|
1120
|
+
expect(ref.current!.getCurrentIndex()).toBe(3);
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
it("loop wraps when scrolling past the end", async () => {
|
|
1124
|
+
const progress = { current: 0 };
|
|
1125
|
+
const Wrapper = createCarousel(progress);
|
|
1126
|
+
const getItemWidth = (i: number) => variableWidths[i];
|
|
1127
|
+
const ref = React.createRef<import("../types").ICarouselInstance>();
|
|
1128
|
+
|
|
1129
|
+
render(
|
|
1130
|
+
<Wrapper
|
|
1131
|
+
ref={ref}
|
|
1132
|
+
data={variableWidths.map((_, i) => `item-${i}`)}
|
|
1133
|
+
style={{ width: 400, height: 200 }}
|
|
1134
|
+
getItemWidth={getItemWidth}
|
|
1135
|
+
loop
|
|
1136
|
+
/>
|
|
1137
|
+
);
|
|
1138
|
+
|
|
1139
|
+
await waitFor(() => expect(ref.current).toBeTruthy(), { timeout: 1000 * 3 });
|
|
1140
|
+
|
|
1141
|
+
// Step past the last item; in loop mode this should wrap to index 0.
|
|
1142
|
+
act(() => {
|
|
1143
|
+
ref.current!.next({ count: 5, animated: false });
|
|
1144
|
+
});
|
|
1145
|
+
act(() => {
|
|
1146
|
+
jest.runOnlyPendingTimers();
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
// After 5 nexts from index 0 in a 5-item loop, we're back at 0.
|
|
1150
|
+
expect(ref.current!.getCurrentIndex()).toBe(0);
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
});
|