react-native-reanimated-carousel 5.0.0-beta.7 → 5.0.0-beta.8

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 (38) hide show
  1. package/README.md +32 -2
  2. package/lib/commonjs/components/CarouselLayout.js +1 -1
  3. package/lib/commonjs/components/CarouselLayout.js.map +1 -1
  4. package/lib/module/components/CarouselLayout.js +1 -1
  5. package/lib/module/components/CarouselLayout.js.map +1 -1
  6. package/lib/typescript/components/CarouselLayout.d.ts.map +1 -1
  7. package/package.json +4 -2
  8. package/src/components/CarouselLayout.tsx +3 -4
  9. package/src/components/Carousel.test.tsx +0 -1215
  10. package/src/components/LazyView.test.tsx +0 -61
  11. package/src/components/Pagination/Pagination.test.tsx +0 -178
  12. package/src/components/ScrollViewGesture.test.tsx +0 -155
  13. package/src/components/issue-899-parallax-reverse-loop.test.tsx +0 -131
  14. package/src/components/rnr-demo.test.tsx +0 -53
  15. package/src/hooks/useAutoPlay.test.ts +0 -194
  16. package/src/hooks/useCarouselController.test.tsx +0 -1158
  17. package/src/hooks/useCheckMounted.test.ts +0 -47
  18. package/src/hooks/useCommonVariables.test.tsx +0 -384
  19. package/src/hooks/useInitProps.test.tsx +0 -134
  20. package/src/hooks/useLayoutConfig.test.tsx +0 -247
  21. package/src/hooks/useOffsetX.test.ts +0 -54
  22. package/src/hooks/useOnProgressChange.test.tsx +0 -270
  23. package/src/hooks/usePanGestureProxy.test.tsx +0 -368
  24. package/src/hooks/usePropsErrorBoundary.test.tsx +0 -57
  25. package/src/hooks/useUpdateGestureConfig.test.ts +0 -89
  26. package/src/hooks/useVisibleRanges.test.tsx +0 -347
  27. package/src/layouts/parallax.test.ts +0 -239
  28. package/src/layouts/stack.test.ts +0 -252
  29. package/src/store/index.test.tsx +0 -314
  30. package/src/utils/compute-gesture-translation.test.ts +0 -70
  31. package/src/utils/compute-offset-if-data-changed.test.ts +0 -133
  32. package/src/utils/compute-offset-if-size-changed.test.ts +0 -78
  33. package/src/utils/computed-with-auto-fill-data.test.ts +0 -298
  34. package/src/utils/deal-with-animation.test.ts +0 -181
  35. package/src/utils/handleroffset-direction.test.ts +0 -124
  36. package/src/utils/index.test.ts +0 -90
  37. package/src/utils/log.test.ts +0 -134
  38. package/src/utils/sanitize-animation-style.test.ts +0 -40
@@ -1,61 +0,0 @@
1
- import { render } from "@testing-library/react-native";
2
- import React from "react";
3
- import { Text } from "react-native";
4
-
5
- import { LazyView } from "./LazyView";
6
-
7
- describe("LazyView", () => {
8
- it("should render children when shouldUpdate is true", () => {
9
- const { getByText } = render(
10
- <LazyView shouldUpdate={true}>
11
- <Text>Test Child</Text>
12
- </LazyView>
13
- );
14
-
15
- expect(getByText("Test Child")).toBeTruthy();
16
- });
17
-
18
- it("should not render children when shouldUpdate is false", () => {
19
- const { queryByText } = render(
20
- <LazyView shouldUpdate={false}>
21
- <Text>Test Child</Text>
22
- </LazyView>
23
- );
24
-
25
- expect(queryByText("Test Child")).toBeNull();
26
- });
27
-
28
- it("should render empty fragment when shouldUpdate is false", () => {
29
- const { toJSON } = render(
30
- <LazyView shouldUpdate={false}>
31
- <Text>Test Child</Text>
32
- </LazyView>
33
- );
34
-
35
- expect(toJSON()).toBeNull();
36
- });
37
-
38
- it("should handle multiple children when shouldUpdate is true", () => {
39
- const { getByText } = render(
40
- <LazyView shouldUpdate={true}>
41
- <Text>First Child</Text>
42
- <Text>Second Child</Text>
43
- </LazyView>
44
- );
45
-
46
- expect(getByText("First Child")).toBeTruthy();
47
- expect(getByText("Second Child")).toBeTruthy();
48
- });
49
-
50
- it("should not render multiple children when shouldUpdate is false", () => {
51
- const { queryByText } = render(
52
- <LazyView shouldUpdate={false}>
53
- <Text>First Child</Text>
54
- <Text>Second Child</Text>
55
- </LazyView>
56
- );
57
-
58
- expect(queryByText("First Child")).toBeNull();
59
- expect(queryByText("Second Child")).toBeNull();
60
- });
61
- });
@@ -1,178 +0,0 @@
1
- import { act, fireEvent, render } from "@testing-library/react-native";
2
- import { View } from "react-native";
3
- import { useSharedValue } from "react-native-reanimated";
4
-
5
- import { Pagination } from ".";
6
-
7
- jest.mock("react-native-worklets", () => ({
8
- scheduleOnRN: (fn: (...args: unknown[]) => void, ...args: unknown[]) => fn(...args),
9
- }));
10
-
11
- // Use fake timers so the reanimated mapper's mockedRequestAnimationFrame
12
- // (a setTimeout-based RAF) doesn't fire outside of act().
13
- beforeEach(() => {
14
- jest.useFakeTimers();
15
- });
16
-
17
- afterEach(() => {
18
- jest.useRealTimers();
19
- });
20
-
21
- describe("Pagination.Basic", () => {
22
- it("throws when size/dot width/height are non-number", () => {
23
- const Test = () => {
24
- const progress = useSharedValue(0);
25
- return (
26
- <Pagination.Basic
27
- data={[1, 2]}
28
- progress={progress}
29
- // @ts-expect-error intentional invalid type
30
- size={"12px"}
31
- />
32
- );
33
- };
34
-
35
- expect(() => render(<Test />)).toThrow("size/width/height must be a number");
36
- });
37
-
38
- it("renders dots and calls onPress", async () => {
39
- const handlePress = jest.fn();
40
- const Test = () => {
41
- const progress = useSharedValue(0);
42
- return (
43
- <Pagination.Basic
44
- data={[1, 2, 3]}
45
- progress={progress}
46
- onPress={handlePress}
47
- carouselName="Demo"
48
- />
49
- );
50
- };
51
-
52
- const { UNSAFE_root } = render(<Test />);
53
- await act(async () => {
54
- jest.advanceTimersByTime(100);
55
- });
56
-
57
- const buttons = UNSAFE_root.findAll(
58
- (node) =>
59
- node?.props?.accessibilityRole === "button" &&
60
- typeof node?.props?.accessibilityLabel === "string" &&
61
- node.props.accessibilityLabel.startsWith("Slide ")
62
- );
63
-
64
- const uniqueByLabel = Array.from(
65
- new Map(buttons.map((b) => [b.props.accessibilityLabel, b])).values()
66
- );
67
-
68
- expect(uniqueByLabel).toHaveLength(3);
69
-
70
- await act(async () => {
71
- fireEvent.press(uniqueByLabel[1]);
72
- jest.advanceTimersByTime(100);
73
- });
74
- expect(handlePress).toHaveBeenCalledWith(1);
75
- });
76
-
77
- it("does not include undefined in default accessibilityLabel when carouselName is omitted", async () => {
78
- const Test = () => {
79
- const progress = useSharedValue(0);
80
- return <Pagination.Basic data={[1, 2]} progress={progress} />;
81
- };
82
-
83
- const { UNSAFE_root } = render(<Test />);
84
- await act(async () => {
85
- jest.advanceTimersByTime(100);
86
- });
87
-
88
- const buttons = UNSAFE_root.findAll(
89
- (node) =>
90
- node?.props?.accessibilityRole === "button" &&
91
- typeof node?.props?.accessibilityLabel === "string" &&
92
- node.props.accessibilityLabel.startsWith("Slide ")
93
- );
94
-
95
- const uniqueByLabel = Array.from(
96
- new Map(buttons.map((b) => [b.props.accessibilityLabel, b])).values()
97
- );
98
-
99
- expect(uniqueByLabel[0].props.accessibilityLabel).toBe("Slide 1 of 2");
100
- expect(uniqueByLabel[0].props.accessibilityLabel).not.toContain("undefined");
101
- });
102
- });
103
-
104
- describe("Pagination.Custom", () => {
105
- it("throws when any size-related prop is non-number", () => {
106
- const Test = () => {
107
- const progress = useSharedValue(0);
108
- return (
109
- <Pagination.Custom
110
- data={[1]}
111
- progress={progress}
112
- // @ts-expect-error intentional invalid type
113
- activeDotStyle={{ width: "10px" }}
114
- />
115
- );
116
- };
117
-
118
- expect(() => render(<Test />)).toThrow("size/width/height must be a number");
119
- });
120
-
121
- it("uses max item dimensions for container min sizes", async () => {
122
- const Test = () => {
123
- const progress = useSharedValue(0);
124
- return (
125
- <Pagination.Custom
126
- data={[1, 2]}
127
- progress={progress}
128
- size={12}
129
- dotStyle={{ width: 14, height: 8 }}
130
- activeDotStyle={{ width: 16, height: 20 }}
131
- />
132
- );
133
- };
134
-
135
- const { UNSAFE_root } = render(<Test />);
136
- await act(async () => {
137
- jest.advanceTimersByTime(100);
138
- });
139
-
140
- // @ts-expect-error
141
- const container = UNSAFE_root.findByType(View);
142
- const flattened = Array.isArray(container.props.style)
143
- ? Object.assign({}, ...container.props.style.filter(Boolean))
144
- : container.props.style;
145
-
146
- expect(flattened.minWidth).toBe(16);
147
- expect(flattened.minHeight).toBe(20);
148
- });
149
-
150
- it("allows overriding pagination item accessibility props", async () => {
151
- const Test = () => {
152
- const progress = useSharedValue(0);
153
- return (
154
- <Pagination.Custom
155
- data={[1, 2]}
156
- progress={progress}
157
- paginationItemAccessibility={(index: number, length: number) => ({
158
- accessibilityLabel: `Page ${index + 1}/${length}`,
159
- accessibilityHint: `Jump to ${index + 1}`,
160
- accessibilityRole: "link",
161
- })}
162
- />
163
- );
164
- };
165
-
166
- const { UNSAFE_root } = render(<Test />);
167
- await act(async () => {
168
- jest.advanceTimersByTime(100);
169
- });
170
-
171
- const item = UNSAFE_root.find(
172
- (node) =>
173
- node?.props?.accessibilityLabel === "Page 1/2" && node?.props?.accessibilityRole === "link"
174
- );
175
-
176
- expect(item.props.accessibilityHint).toBe("Jump to 1");
177
- });
178
- });
@@ -1,155 +0,0 @@
1
- import React from "react";
2
- import { View } from "react-native";
3
- import type {
4
- GestureStateChangeEvent,
5
- GestureUpdateEvent,
6
- PanGestureHandlerEventPayload,
7
- } from "react-native-gesture-handler";
8
- import type { SharedValue } from "react-native-reanimated";
9
-
10
- import { act, render } from "@testing-library/react-native";
11
-
12
- import { ScrollViewGesture } from "./ScrollViewGesture";
13
-
14
- import { usePanGestureProxy } from "../hooks/usePanGestureProxy";
15
- import { GlobalStateContext, type IContext } from "../store";
16
-
17
- jest.mock("../hooks/usePanGestureProxy", () => ({
18
- usePanGestureProxy: jest.fn(() => ({})),
19
- }));
20
-
21
- const mockUsePanGestureProxy = jest.mocked(usePanGestureProxy);
22
-
23
- jest.mock("react-native-gesture-handler", () => {
24
- const React = require("react");
25
- const actual = jest.requireActual("react-native-gesture-handler");
26
-
27
- return {
28
- ...actual,
29
- GestureDetector: ({ children }: { children: React.ReactNode }) => {
30
- const child = React.Children.only(children);
31
-
32
- // Simulate the upstream web path that reads `element.ref`.
33
- // React 19 warns only when the child element actually carries a ref.
34
- // @ts-expect-error React 19 removed `element.ref` from the element type.
35
- void child.ref;
36
-
37
- return child;
38
- },
39
- };
40
- });
41
-
42
- type RenderGestureOptions = {
43
- vertical?: boolean;
44
- size?: number;
45
- dataLength?: number;
46
- containerSize?: { width: number; height: number };
47
- };
48
-
49
- function renderGesture({
50
- vertical = false,
51
- size = 300,
52
- dataLength = 3,
53
- containerSize = { width: 300, height: 200 },
54
- }: RenderGestureOptions = {}) {
55
- const translation = { value: 0 } as SharedValue<number>;
56
- const contextValue = {
57
- props: {
58
- onConfigurePanGesture: undefined,
59
- vertical,
60
- pagingEnabled: true,
61
- snapEnabled: true,
62
- loop: false,
63
- scrollAnimationDuration: 500,
64
- withAnimation: undefined,
65
- enabled: true,
66
- dataLength,
67
- overscrollEnabled: false,
68
- maxScrollDistancePerSwipe: undefined,
69
- minScrollDistancePerSwipe: undefined,
70
- fixedDirection: undefined,
71
- },
72
- common: {
73
- size,
74
- validLength: dataLength,
75
- handlerOffset: { value: 0 },
76
- resolvedSize: { value: size },
77
- sizePhase: { value: "ready" },
78
- sizeExplicit: true,
79
- },
80
- layout: {
81
- containerSize: { value: containerSize },
82
- updateContainerSize: jest.fn(),
83
- itemDimensions: { value: {} },
84
- updateItemDimensions: jest.fn(),
85
- },
86
- } as unknown as IContext;
87
-
88
- render(
89
- <GlobalStateContext.Provider value={contextValue}>
90
- <ScrollViewGesture size={size} translation={translation} style={containerSize}>
91
- <View testID="content" />
92
- </ScrollViewGesture>
93
- </GlobalStateContext.Provider>
94
- );
95
-
96
- const gestureCallbacks =
97
- mockUsePanGestureProxy.mock.calls[mockUsePanGestureProxy.mock.calls.length - 1]?.[0];
98
-
99
- if (!gestureCallbacks) {
100
- throw new Error("Expected ScrollViewGesture to configure the pan gesture");
101
- }
102
-
103
- return { gestureCallbacks, translation };
104
- }
105
-
106
- describe("issue #857 web regression", () => {
107
- beforeEach(() => {
108
- mockUsePanGestureProxy.mockClear();
109
- });
110
-
111
- it("does not emit the React 19 element.ref warning from the gesture path", () => {
112
- const errorSpy = jest.spyOn(console, "error").mockImplementation(() => {});
113
-
114
- try {
115
- renderGesture();
116
-
117
- const react19RefWarnings = errorSpy.mock.calls.filter(([message]) =>
118
- String(message).includes("Accessing element.ref was removed in React 19")
119
- );
120
-
121
- expect(react19RefWarnings).toHaveLength(0);
122
- } finally {
123
- errorSpy.mockRestore();
124
- }
125
- });
126
-
127
- it.each([
128
- ["horizontal", false, 300, 3, { width: 300, height: 200 }, -600],
129
- ["vertical", true, 300, 3, { width: 300, height: 200 }, -700],
130
- ["unmeasured container", false, 300, 3, { width: 0, height: 0 }, 0],
131
- ["content smaller than its container", false, 100, 2, { width: 300, height: 200 }, 0],
132
- ] as const)(
133
- "uses the %s main-axis container size for the non-loop boundary",
134
- (_name, vertical, size, dataLength, containerSize, expectedTranslation) => {
135
- const { gestureCallbacks, translation } = renderGesture({
136
- vertical,
137
- size,
138
- dataLength,
139
- containerSize,
140
- });
141
-
142
- act(() => {
143
- gestureCallbacks.onGestureStart(
144
- {} as GestureStateChangeEvent<PanGestureHandlerEventPayload>
145
- );
146
- gestureCallbacks.onGestureUpdate({
147
- translationX: -1000,
148
- translationY: -1000,
149
- } as GestureUpdateEvent<PanGestureHandlerEventPayload>);
150
- });
151
-
152
- expect(translation.value).toBeCloseTo(expectedTranslation);
153
- }
154
- );
155
- });
@@ -1,131 +0,0 @@
1
- import React from "react";
2
- import { StyleSheet } from "react-native";
3
- import type { PanGesture } from "react-native-gesture-handler";
4
- import { Gesture, State } from "react-native-gesture-handler";
5
- import Animated, { useDerivedValue, useSharedValue } from "react-native-reanimated";
6
-
7
- import { act, render, waitFor } from "@testing-library/react-native";
8
- import { fireGestureHandler, getByGestureTestId } from "react-native-gesture-handler/jest-utils";
9
-
10
- import Carousel from "./Carousel";
11
-
12
- {
13
- const cfg = (global as any).__reanimatedLoggerConfig as
14
- | { logFunction: (data: { level: number; message: string }) => void }
15
- | undefined;
16
- if (cfg) {
17
- const originalLog = cfg.logFunction;
18
- cfg.logFunction = (data) => {
19
- if (data.message.includes("measure() cannot be used with Jest")) return;
20
- originalLog(data);
21
- };
22
- }
23
- }
24
-
25
- const slideWidth = 300;
26
- const slideHeight = 200;
27
- const gestureTestId = "rnrc-gesture-handler";
28
- const realPan = Gesture.Pan();
29
-
30
- jest.spyOn(Gesture, "Pan").mockImplementation(() => realPan.withTestId(gestureTestId));
31
-
32
- describe("issue #899 parallax reverse loop regression", () => {
33
- beforeEach(() => {
34
- jest.useFakeTimers();
35
- });
36
-
37
- afterEach(() => {
38
- act(() => {
39
- jest.runOnlyPendingTimers();
40
- });
41
- jest.useRealTimers();
42
- jest.clearAllTimers();
43
- });
44
-
45
- it("preserves renderItem styles and props when looping backward from index 0 in parallax mode", async () => {
46
- const progress = { current: 0 };
47
- const data = Array.from({ length: 30 }, (_, index) => ({
48
- id: `item-${index}`,
49
- color: index === 29 ? "#111111" : `rgb(${index}, ${index}, ${index})`,
50
- }));
51
-
52
- const Wrapper = () => {
53
- const progressAnimVal = useSharedValue(progress.current);
54
-
55
- useDerivedValue(() => {
56
- progress.current = progressAnimVal.value;
57
- }, [progressAnimVal]);
58
-
59
- return (
60
- <Carousel
61
- data={data}
62
- defaultIndex={0}
63
- loop
64
- mode="parallax"
65
- windowSize={5}
66
- style={{ width: slideWidth, height: slideHeight }}
67
- onProgressChange={(_, absoluteProgress) => {
68
- progressAnimVal.value = absoluteProgress;
69
- }}
70
- renderItem={({ item, index }) => (
71
- <Animated.View
72
- testID={`issue-899-item-${index}`}
73
- accessibilityLabel={`issue-899-label-${index}`}
74
- style={{
75
- width: slideWidth,
76
- height: slideHeight,
77
- backgroundColor: item.color,
78
- }}
79
- />
80
- )}
81
- />
82
- );
83
- };
84
-
85
- const { getByTestId, queryByTestId } = render(<Wrapper />);
86
-
87
- await waitFor(() => {
88
- expect(getByTestId("issue-899-item-0")).toBeTruthy();
89
- });
90
-
91
- const prerenderedWrappedItem = getByTestId("issue-899-item-29");
92
- const prerenderedWrappedStyle = StyleSheet.flatten(prerenderedWrappedItem.props.style);
93
-
94
- expect(prerenderedWrappedItem.props.accessibilityLabel).toBe("issue-899-label-29");
95
- expect(prerenderedWrappedStyle.backgroundColor).toBe("#111111");
96
-
97
- fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
98
- { state: State.BEGAN, translationX: 0, velocityX: slideWidth },
99
- ]);
100
-
101
- fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
102
- { state: State.ACTIVE, translationX: slideWidth * 0.7, velocityX: slideWidth },
103
- ]);
104
-
105
- expect(queryByTestId("issue-899-item-29")).toBeTruthy();
106
-
107
- await waitFor(() => {
108
- expect(queryByTestId("issue-899-item-29")).toBeTruthy();
109
- });
110
-
111
- const activeWrappedItem = getByTestId("issue-899-item-29");
112
- const activeWrappedStyle = StyleSheet.flatten(activeWrappedItem.props.style);
113
-
114
- expect(activeWrappedItem.props.accessibilityLabel).toBe("issue-899-label-29");
115
- expect(activeWrappedStyle.backgroundColor).toBe("#111111");
116
-
117
- fireGestureHandler<PanGesture>(getByGestureTestId(gestureTestId), [
118
- { state: State.END, translationX: slideWidth, velocityX: slideWidth },
119
- ]);
120
-
121
- await waitFor(() => {
122
- expect(progress.current).toBe(29);
123
- });
124
-
125
- const wrappedItem = getByTestId("issue-899-item-29");
126
- const flattenedStyle = StyleSheet.flatten(wrappedItem.props.style);
127
-
128
- expect(wrappedItem.props.accessibilityLabel).toBe("issue-899-label-29");
129
- expect(flattenedStyle.backgroundColor).toBe("#111111");
130
- });
131
- });
@@ -1,53 +0,0 @@
1
- import { render } from "@testing-library/react-native";
2
- import type { FC } from "react";
3
- import React from "react";
4
- import Animated, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
5
-
6
- describe("Carousel Animations", () => {
7
- beforeEach(() => {
8
- jest.useFakeTimers();
9
- });
10
-
11
- afterEach(() => {
12
- jest.runOnlyPendingTimers();
13
- jest.useRealTimers();
14
- });
15
-
16
- it("animates opacity with timing", () => {
17
- const TestComponent: FC<{ value: number }> = ({ value }) => {
18
- const sharedValue = useSharedValue(value);
19
-
20
- // Using withTiming to wrap the value change, so it can be tested
21
- const animatedStyle = useAnimatedStyle(() => ({
22
- opacity: withTiming(sharedValue.value, { duration: 500 }),
23
- }));
24
-
25
- // When the prop changes, update the shared value
26
- React.useEffect(() => {
27
- sharedValue.value = value;
28
- }, [value, sharedValue]);
29
-
30
- return <Animated.View testID="animated-view" style={animatedStyle} />;
31
- };
32
-
33
- const initialValue = 0;
34
- const updatedValue = 1;
35
-
36
- const { getByTestId, rerender } = render(<TestComponent value={initialValue} />);
37
- const view = getByTestId("animated-view");
38
-
39
- // Verify the initial state
40
- expect(view).toHaveAnimatedStyle({ opacity: initialValue });
41
-
42
- // Trigger the value change
43
- rerender(<TestComponent value={updatedValue} />);
44
-
45
- // Advance the animation to the middle point
46
- jest.advanceTimersByTime(250);
47
- expect(view).toHaveAnimatedStyle({ opacity: 0.5 }); // Animation middle value
48
-
49
- // Advance the animation to the completion
50
- jest.advanceTimersByTime(250);
51
- expect(view).toHaveAnimatedStyle({ opacity: updatedValue }); // Animation completion
52
- });
53
- });