@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.
Files changed (328) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +652 -0
  3. package/lib/commonjs/components/Carousel.js +2 -0
  4. package/lib/commonjs/components/Carousel.js.map +1 -0
  5. package/lib/commonjs/components/CarouselLayout.js +2 -0
  6. package/lib/commonjs/components/CarouselLayout.js.map +1 -0
  7. package/lib/commonjs/components/ItemLayout.js +2 -0
  8. package/lib/commonjs/components/ItemLayout.js.map +1 -0
  9. package/lib/commonjs/components/ItemRenderer.js +2 -0
  10. package/lib/commonjs/components/ItemRenderer.js.map +1 -0
  11. package/lib/commonjs/components/LazyView.js +2 -0
  12. package/lib/commonjs/components/LazyView.js.map +1 -0
  13. package/lib/commonjs/components/Pagination/Basic/PaginationItem.js +2 -0
  14. package/lib/commonjs/components/Pagination/Basic/PaginationItem.js.map +1 -0
  15. package/lib/commonjs/components/Pagination/Basic/index.js +2 -0
  16. package/lib/commonjs/components/Pagination/Basic/index.js.map +1 -0
  17. package/lib/commonjs/components/Pagination/Custom/PaginationItem.js +2 -0
  18. package/lib/commonjs/components/Pagination/Custom/PaginationItem.js.map +1 -0
  19. package/lib/commonjs/components/Pagination/Custom/index.js +2 -0
  20. package/lib/commonjs/components/Pagination/Custom/index.js.map +1 -0
  21. package/lib/commonjs/components/Pagination/index.js +2 -0
  22. package/lib/commonjs/components/Pagination/index.js.map +1 -0
  23. package/lib/commonjs/components/ScrollViewGesture.js +2 -0
  24. package/lib/commonjs/components/ScrollViewGesture.js.map +1 -0
  25. package/lib/commonjs/constants/index.js +2 -0
  26. package/lib/commonjs/constants/index.js.map +1 -0
  27. package/lib/commonjs/hooks/useAutoPlay.js +2 -0
  28. package/lib/commonjs/hooks/useAutoPlay.js.map +1 -0
  29. package/lib/commonjs/hooks/useCarouselController.js +2 -0
  30. package/lib/commonjs/hooks/useCarouselController.js.map +1 -0
  31. package/lib/commonjs/hooks/useCheckMounted.js +2 -0
  32. package/lib/commonjs/hooks/useCheckMounted.js.map +1 -0
  33. package/lib/commonjs/hooks/useCommonVariables.js +2 -0
  34. package/lib/commonjs/hooks/useCommonVariables.js.map +1 -0
  35. package/lib/commonjs/hooks/useInitProps.js +2 -0
  36. package/lib/commonjs/hooks/useInitProps.js.map +1 -0
  37. package/lib/commonjs/hooks/useLayoutConfig.js +2 -0
  38. package/lib/commonjs/hooks/useLayoutConfig.js.map +1 -0
  39. package/lib/commonjs/hooks/useOffsetX.js +2 -0
  40. package/lib/commonjs/hooks/useOffsetX.js.map +1 -0
  41. package/lib/commonjs/hooks/useOnProgressChange.js +2 -0
  42. package/lib/commonjs/hooks/useOnProgressChange.js.map +1 -0
  43. package/lib/commonjs/hooks/usePanGestureProxy.js +2 -0
  44. package/lib/commonjs/hooks/usePanGestureProxy.js.map +1 -0
  45. package/lib/commonjs/hooks/usePropsErrorBoundary.js +2 -0
  46. package/lib/commonjs/hooks/usePropsErrorBoundary.js.map +1 -0
  47. package/lib/commonjs/hooks/useSizeResolver.js +2 -0
  48. package/lib/commonjs/hooks/useSizeResolver.js.map +1 -0
  49. package/lib/commonjs/hooks/useUpdateGestureConfig.js +2 -0
  50. package/lib/commonjs/hooks/useUpdateGestureConfig.js.map +1 -0
  51. package/lib/commonjs/hooks/useVisibleRanges.js +2 -0
  52. package/lib/commonjs/hooks/useVisibleRanges.js.map +1 -0
  53. package/lib/commonjs/index.js +2 -0
  54. package/lib/commonjs/index.js.map +1 -0
  55. package/lib/commonjs/layouts/index.js +2 -0
  56. package/lib/commonjs/layouts/index.js.map +1 -0
  57. package/lib/commonjs/layouts/normal.js +2 -0
  58. package/lib/commonjs/layouts/normal.js.map +1 -0
  59. package/lib/commonjs/layouts/parallax.js +2 -0
  60. package/lib/commonjs/layouts/parallax.js.map +1 -0
  61. package/lib/commonjs/layouts/stack.js +2 -0
  62. package/lib/commonjs/layouts/stack.js.map +1 -0
  63. package/lib/commonjs/package.json +1 -0
  64. package/lib/commonjs/store/index.js +2 -0
  65. package/lib/commonjs/store/index.js.map +1 -0
  66. package/lib/commonjs/types.js +2 -0
  67. package/lib/commonjs/types.js.map +1 -0
  68. package/lib/commonjs/utils/compute-gesture-translation.js +2 -0
  69. package/lib/commonjs/utils/compute-gesture-translation.js.map +1 -0
  70. package/lib/commonjs/utils/compute-offset-if-data-changed.js +2 -0
  71. package/lib/commonjs/utils/compute-offset-if-data-changed.js.map +1 -0
  72. package/lib/commonjs/utils/compute-offset-if-size-changed.js +2 -0
  73. package/lib/commonjs/utils/compute-offset-if-size-changed.js.map +1 -0
  74. package/lib/commonjs/utils/compute-offset-if-sizes-changed.js +2 -0
  75. package/lib/commonjs/utils/compute-offset-if-sizes-changed.js.map +1 -0
  76. package/lib/commonjs/utils/computed-with-auto-fill-data.js +2 -0
  77. package/lib/commonjs/utils/computed-with-auto-fill-data.js.map +1 -0
  78. package/lib/commonjs/utils/deal-with-animation.js +2 -0
  79. package/lib/commonjs/utils/deal-with-animation.js.map +1 -0
  80. package/lib/commonjs/utils/handleroffset-direction.js +2 -0
  81. package/lib/commonjs/utils/handleroffset-direction.js.map +1 -0
  82. package/lib/commonjs/utils/log.js +2 -0
  83. package/lib/commonjs/utils/log.js.map +1 -0
  84. package/lib/commonjs/utils/sanitize-animation-style.js +2 -0
  85. package/lib/commonjs/utils/sanitize-animation-style.js.map +1 -0
  86. package/lib/commonjs/utils/size-resolver.js +2 -0
  87. package/lib/commonjs/utils/size-resolver.js.map +1 -0
  88. package/lib/module/components/Carousel.js +2 -0
  89. package/lib/module/components/Carousel.js.map +1 -0
  90. package/lib/module/components/CarouselLayout.js +2 -0
  91. package/lib/module/components/CarouselLayout.js.map +1 -0
  92. package/lib/module/components/ItemLayout.js +2 -0
  93. package/lib/module/components/ItemLayout.js.map +1 -0
  94. package/lib/module/components/ItemRenderer.js +2 -0
  95. package/lib/module/components/ItemRenderer.js.map +1 -0
  96. package/lib/module/components/LazyView.js +2 -0
  97. package/lib/module/components/LazyView.js.map +1 -0
  98. package/lib/module/components/Pagination/Basic/PaginationItem.js +2 -0
  99. package/lib/module/components/Pagination/Basic/PaginationItem.js.map +1 -0
  100. package/lib/module/components/Pagination/Basic/index.js +2 -0
  101. package/lib/module/components/Pagination/Basic/index.js.map +1 -0
  102. package/lib/module/components/Pagination/Custom/PaginationItem.js +2 -0
  103. package/lib/module/components/Pagination/Custom/PaginationItem.js.map +1 -0
  104. package/lib/module/components/Pagination/Custom/index.js +2 -0
  105. package/lib/module/components/Pagination/Custom/index.js.map +1 -0
  106. package/lib/module/components/Pagination/index.js +2 -0
  107. package/lib/module/components/Pagination/index.js.map +1 -0
  108. package/lib/module/components/ScrollViewGesture.js +2 -0
  109. package/lib/module/components/ScrollViewGesture.js.map +1 -0
  110. package/lib/module/constants/index.js +2 -0
  111. package/lib/module/constants/index.js.map +1 -0
  112. package/lib/module/hooks/useAutoPlay.js +2 -0
  113. package/lib/module/hooks/useAutoPlay.js.map +1 -0
  114. package/lib/module/hooks/useCarouselController.js +2 -0
  115. package/lib/module/hooks/useCarouselController.js.map +1 -0
  116. package/lib/module/hooks/useCheckMounted.js +2 -0
  117. package/lib/module/hooks/useCheckMounted.js.map +1 -0
  118. package/lib/module/hooks/useCommonVariables.js +2 -0
  119. package/lib/module/hooks/useCommonVariables.js.map +1 -0
  120. package/lib/module/hooks/useInitProps.js +2 -0
  121. package/lib/module/hooks/useInitProps.js.map +1 -0
  122. package/lib/module/hooks/useLayoutConfig.js +2 -0
  123. package/lib/module/hooks/useLayoutConfig.js.map +1 -0
  124. package/lib/module/hooks/useOffsetX.js +2 -0
  125. package/lib/module/hooks/useOffsetX.js.map +1 -0
  126. package/lib/module/hooks/useOnProgressChange.js +2 -0
  127. package/lib/module/hooks/useOnProgressChange.js.map +1 -0
  128. package/lib/module/hooks/usePanGestureProxy.js +2 -0
  129. package/lib/module/hooks/usePanGestureProxy.js.map +1 -0
  130. package/lib/module/hooks/usePropsErrorBoundary.js +2 -0
  131. package/lib/module/hooks/usePropsErrorBoundary.js.map +1 -0
  132. package/lib/module/hooks/useSizeResolver.js +2 -0
  133. package/lib/module/hooks/useSizeResolver.js.map +1 -0
  134. package/lib/module/hooks/useUpdateGestureConfig.js +2 -0
  135. package/lib/module/hooks/useUpdateGestureConfig.js.map +1 -0
  136. package/lib/module/hooks/useVisibleRanges.js +2 -0
  137. package/lib/module/hooks/useVisibleRanges.js.map +1 -0
  138. package/lib/module/index.js +2 -0
  139. package/lib/module/index.js.map +1 -0
  140. package/lib/module/layouts/index.js +2 -0
  141. package/lib/module/layouts/index.js.map +1 -0
  142. package/lib/module/layouts/normal.js +2 -0
  143. package/lib/module/layouts/normal.js.map +1 -0
  144. package/lib/module/layouts/parallax.js +2 -0
  145. package/lib/module/layouts/parallax.js.map +1 -0
  146. package/lib/module/layouts/stack.js +2 -0
  147. package/lib/module/layouts/stack.js.map +1 -0
  148. package/lib/module/store/index.js +2 -0
  149. package/lib/module/store/index.js.map +1 -0
  150. package/lib/module/types.js +2 -0
  151. package/lib/module/types.js.map +1 -0
  152. package/lib/module/utils/compute-gesture-translation.js +2 -0
  153. package/lib/module/utils/compute-gesture-translation.js.map +1 -0
  154. package/lib/module/utils/compute-offset-if-data-changed.js +2 -0
  155. package/lib/module/utils/compute-offset-if-data-changed.js.map +1 -0
  156. package/lib/module/utils/compute-offset-if-size-changed.js +2 -0
  157. package/lib/module/utils/compute-offset-if-size-changed.js.map +1 -0
  158. package/lib/module/utils/compute-offset-if-sizes-changed.js +2 -0
  159. package/lib/module/utils/compute-offset-if-sizes-changed.js.map +1 -0
  160. package/lib/module/utils/computed-with-auto-fill-data.js +2 -0
  161. package/lib/module/utils/computed-with-auto-fill-data.js.map +1 -0
  162. package/lib/module/utils/deal-with-animation.js +2 -0
  163. package/lib/module/utils/deal-with-animation.js.map +1 -0
  164. package/lib/module/utils/handleroffset-direction.js +2 -0
  165. package/lib/module/utils/handleroffset-direction.js.map +1 -0
  166. package/lib/module/utils/log.js +2 -0
  167. package/lib/module/utils/log.js.map +1 -0
  168. package/lib/module/utils/sanitize-animation-style.js +2 -0
  169. package/lib/module/utils/sanitize-animation-style.js.map +1 -0
  170. package/lib/module/utils/size-resolver.js +2 -0
  171. package/lib/module/utils/size-resolver.js.map +1 -0
  172. package/lib/typescript/components/Carousel.d.ts +8 -0
  173. package/lib/typescript/components/Carousel.d.ts.map +1 -0
  174. package/lib/typescript/components/CarouselLayout.d.ts +6 -0
  175. package/lib/typescript/components/CarouselLayout.d.ts.map +1 -0
  176. package/lib/typescript/components/ItemLayout.d.ts +15 -0
  177. package/lib/typescript/components/ItemLayout.d.ts.map +1 -0
  178. package/lib/typescript/components/ItemRenderer.d.ts +24 -0
  179. package/lib/typescript/components/ItemRenderer.d.ts.map +1 -0
  180. package/lib/typescript/components/LazyView.d.ts +8 -0
  181. package/lib/typescript/components/LazyView.d.ts.map +1 -0
  182. package/lib/typescript/components/Pagination/Basic/PaginationItem.d.ts +29 -0
  183. package/lib/typescript/components/Pagination/Basic/PaginationItem.d.ts.map +1 -0
  184. package/lib/typescript/components/Pagination/Basic/index.d.ts +23 -0
  185. package/lib/typescript/components/Pagination/Basic/index.d.ts.map +1 -0
  186. package/lib/typescript/components/Pagination/Custom/PaginationItem.d.ts +35 -0
  187. package/lib/typescript/components/Pagination/Custom/PaginationItem.d.ts.map +1 -0
  188. package/lib/typescript/components/Pagination/Custom/index.d.ts +26 -0
  189. package/lib/typescript/components/Pagination/Custom/index.d.ts.map +1 -0
  190. package/lib/typescript/components/Pagination/index.d.ts +5 -0
  191. package/lib/typescript/components/Pagination/index.d.ts.map +1 -0
  192. package/lib/typescript/components/ScrollViewGesture.d.ts +19 -0
  193. package/lib/typescript/components/ScrollViewGesture.d.ts.map +1 -0
  194. package/lib/typescript/constants/index.d.ts +9 -0
  195. package/lib/typescript/constants/index.d.ts.map +1 -0
  196. package/lib/typescript/hooks/useAutoPlay.d.ts +12 -0
  197. package/lib/typescript/hooks/useAutoPlay.d.ts.map +1 -0
  198. package/lib/typescript/hooks/useCarouselController.d.ts +28 -0
  199. package/lib/typescript/hooks/useCarouselController.d.ts.map +1 -0
  200. package/lib/typescript/hooks/useCheckMounted.d.ts +3 -0
  201. package/lib/typescript/hooks/useCheckMounted.d.ts.map +1 -0
  202. package/lib/typescript/hooks/useCommonVariables.d.ts +13 -0
  203. package/lib/typescript/hooks/useCommonVariables.d.ts.map +1 -0
  204. package/lib/typescript/hooks/useInitProps.d.ts +17 -0
  205. package/lib/typescript/hooks/useInitProps.d.ts.map +1 -0
  206. package/lib/typescript/hooks/useLayoutConfig.d.ts +10 -0
  207. package/lib/typescript/hooks/useLayoutConfig.d.ts.map +1 -0
  208. package/lib/typescript/hooks/useOffsetX.d.ts +24 -0
  209. package/lib/typescript/hooks/useOffsetX.d.ts.map +1 -0
  210. package/lib/typescript/hooks/useOnProgressChange.d.ts +14 -0
  211. package/lib/typescript/hooks/useOnProgressChange.d.ts.map +1 -0
  212. package/lib/typescript/hooks/usePanGestureProxy.d.ts +10 -0
  213. package/lib/typescript/hooks/usePanGestureProxy.d.ts.map +1 -0
  214. package/lib/typescript/hooks/usePropsErrorBoundary.d.ts +5 -0
  215. package/lib/typescript/hooks/usePropsErrorBoundary.d.ts.map +1 -0
  216. package/lib/typescript/hooks/useSizeResolver.d.ts +27 -0
  217. package/lib/typescript/hooks/useSizeResolver.d.ts.map +1 -0
  218. package/lib/typescript/hooks/useUpdateGestureConfig.d.ts +6 -0
  219. package/lib/typescript/hooks/useUpdateGestureConfig.d.ts.map +1 -0
  220. package/lib/typescript/hooks/useVisibleRanges.d.ts +23 -0
  221. package/lib/typescript/hooks/useVisibleRanges.d.ts.map +1 -0
  222. package/lib/typescript/index.d.ts +7 -0
  223. package/lib/typescript/index.d.ts.map +1 -0
  224. package/lib/typescript/layouts/index.d.ts +11 -0
  225. package/lib/typescript/layouts/index.d.ts.map +1 -0
  226. package/lib/typescript/layouts/normal.d.ts +16 -0
  227. package/lib/typescript/layouts/normal.d.ts.map +1 -0
  228. package/lib/typescript/layouts/parallax.d.ts +50 -0
  229. package/lib/typescript/layouts/parallax.d.ts.map +1 -0
  230. package/lib/typescript/layouts/stack.d.ts +38 -0
  231. package/lib/typescript/layouts/stack.d.ts.map +1 -0
  232. package/lib/typescript/store/index.d.ts +38 -0
  233. package/lib/typescript/store/index.d.ts.map +1 -0
  234. package/lib/typescript/types.d.ts +326 -0
  235. package/lib/typescript/types.d.ts.map +1 -0
  236. package/lib/typescript/utils/compute-gesture-translation.d.ts +9 -0
  237. package/lib/typescript/utils/compute-gesture-translation.d.ts.map +1 -0
  238. package/lib/typescript/utils/compute-offset-if-data-changed.d.ts +9 -0
  239. package/lib/typescript/utils/compute-offset-if-data-changed.d.ts.map +1 -0
  240. package/lib/typescript/utils/compute-offset-if-size-changed.d.ts +6 -0
  241. package/lib/typescript/utils/compute-offset-if-size-changed.d.ts.map +1 -0
  242. package/lib/typescript/utils/compute-offset-if-sizes-changed.d.ts +14 -0
  243. package/lib/typescript/utils/compute-offset-if-sizes-changed.d.ts.map +1 -0
  244. package/lib/typescript/utils/computed-with-auto-fill-data.d.ts +23 -0
  245. package/lib/typescript/utils/computed-with-auto-fill-data.d.ts.map +1 -0
  246. package/lib/typescript/utils/deal-with-animation.d.ts +3 -0
  247. package/lib/typescript/utils/deal-with-animation.d.ts.map +1 -0
  248. package/lib/typescript/utils/handleroffset-direction.d.ts +4 -0
  249. package/lib/typescript/utils/handleroffset-direction.d.ts.map +1 -0
  250. package/lib/typescript/utils/log.d.ts +7 -0
  251. package/lib/typescript/utils/log.d.ts.map +1 -0
  252. package/lib/typescript/utils/sanitize-animation-style.d.ts +3 -0
  253. package/lib/typescript/utils/sanitize-animation-style.d.ts.map +1 -0
  254. package/lib/typescript/utils/size-resolver.d.ts +87 -0
  255. package/lib/typescript/utils/size-resolver.d.ts.map +1 -0
  256. package/package.json +151 -0
  257. package/src/components/Carousel.test.tsx +1153 -0
  258. package/src/components/Carousel.tsx +35 -0
  259. package/src/components/CarouselLayout.tsx +231 -0
  260. package/src/components/ItemLayout.tsx +217 -0
  261. package/src/components/ItemRenderer.tsx +114 -0
  262. package/src/components/LazyView.test.tsx +61 -0
  263. package/src/components/LazyView.tsx +14 -0
  264. package/src/components/Pagination/Basic/PaginationItem.tsx +149 -0
  265. package/src/components/Pagination/Basic/index.tsx +98 -0
  266. package/src/components/Pagination/Custom/PaginationItem.tsx +166 -0
  267. package/src/components/Pagination/Custom/index.tsx +111 -0
  268. package/src/components/Pagination/Pagination.test.tsx +178 -0
  269. package/src/components/Pagination/index.tsx +7 -0
  270. package/src/components/ScrollViewGesture.tsx +577 -0
  271. package/src/components/rnr-demo.test.tsx +53 -0
  272. package/src/constants/index.ts +11 -0
  273. package/src/hooks/useAutoPlay.test.ts +194 -0
  274. package/src/hooks/useAutoPlay.ts +58 -0
  275. package/src/hooks/useCarouselController.test.tsx +1158 -0
  276. package/src/hooks/useCarouselController.tsx +525 -0
  277. package/src/hooks/useCheckMounted.test.ts +47 -0
  278. package/src/hooks/useCheckMounted.ts +14 -0
  279. package/src/hooks/useCommonVariables.test.tsx +384 -0
  280. package/src/hooks/useCommonVariables.ts +202 -0
  281. package/src/hooks/useInitProps.test.tsx +134 -0
  282. package/src/hooks/useInitProps.ts +111 -0
  283. package/src/hooks/useLayoutConfig.test.tsx +247 -0
  284. package/src/hooks/useLayoutConfig.ts +30 -0
  285. package/src/hooks/useOffsetX.test.ts +110 -0
  286. package/src/hooks/useOffsetX.ts +109 -0
  287. package/src/hooks/useOnProgressChange.test.tsx +207 -0
  288. package/src/hooks/useOnProgressChange.ts +105 -0
  289. package/src/hooks/usePanGestureProxy.test.tsx +368 -0
  290. package/src/hooks/usePanGestureProxy.ts +144 -0
  291. package/src/hooks/usePropsErrorBoundary.ts +138 -0
  292. package/src/hooks/useSizeResolver.test.tsx +112 -0
  293. package/src/hooks/useSizeResolver.ts +106 -0
  294. package/src/hooks/useUpdateGestureConfig.test.ts +89 -0
  295. package/src/hooks/useUpdateGestureConfig.ts +14 -0
  296. package/src/hooks/useVisibleRanges.test.tsx +366 -0
  297. package/src/hooks/useVisibleRanges.tsx +123 -0
  298. package/src/index.tsx +13 -0
  299. package/src/layouts/index.tsx +12 -0
  300. package/src/layouts/normal.ts +32 -0
  301. package/src/layouts/parallax.test.ts +239 -0
  302. package/src/layouts/parallax.ts +83 -0
  303. package/src/layouts/stack.test.ts +252 -0
  304. package/src/layouts/stack.ts +306 -0
  305. package/src/store/index.test.tsx +314 -0
  306. package/src/store/index.tsx +66 -0
  307. package/src/types.ts +348 -0
  308. package/src/utils/compute-gesture-translation.test.ts +70 -0
  309. package/src/utils/compute-gesture-translation.ts +29 -0
  310. package/src/utils/compute-offset-if-data-changed.test.ts +133 -0
  311. package/src/utils/compute-offset-if-data-changed.ts +44 -0
  312. package/src/utils/compute-offset-if-size-changed.test.ts +78 -0
  313. package/src/utils/compute-offset-if-size-changed.ts +14 -0
  314. package/src/utils/compute-offset-if-sizes-changed.test.ts +74 -0
  315. package/src/utils/compute-offset-if-sizes-changed.ts +44 -0
  316. package/src/utils/computed-with-auto-fill-data.test.ts +298 -0
  317. package/src/utils/computed-with-auto-fill-data.ts +92 -0
  318. package/src/utils/deal-with-animation.test.ts +181 -0
  319. package/src/utils/deal-with-animation.ts +17 -0
  320. package/src/utils/handleroffset-direction.test.ts +124 -0
  321. package/src/utils/handleroffset-direction.ts +18 -0
  322. package/src/utils/index.test.ts +90 -0
  323. package/src/utils/log.test.ts +134 -0
  324. package/src/utils/log.ts +12 -0
  325. package/src/utils/sanitize-animation-style.test.ts +40 -0
  326. package/src/utils/sanitize-animation-style.ts +20 -0
  327. package/src/utils/size-resolver.test.ts +193 -0
  328. 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
+ });