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.
- package/README.md +32 -2
- package/lib/commonjs/components/CarouselLayout.js +1 -1
- package/lib/commonjs/components/CarouselLayout.js.map +1 -1
- package/lib/module/components/CarouselLayout.js +1 -1
- package/lib/module/components/CarouselLayout.js.map +1 -1
- package/lib/typescript/components/CarouselLayout.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/components/CarouselLayout.tsx +3 -4
- package/src/components/Carousel.test.tsx +0 -1215
- package/src/components/LazyView.test.tsx +0 -61
- package/src/components/Pagination/Pagination.test.tsx +0 -178
- package/src/components/ScrollViewGesture.test.tsx +0 -155
- package/src/components/issue-899-parallax-reverse-loop.test.tsx +0 -131
- package/src/components/rnr-demo.test.tsx +0 -53
- package/src/hooks/useAutoPlay.test.ts +0 -194
- package/src/hooks/useCarouselController.test.tsx +0 -1158
- package/src/hooks/useCheckMounted.test.ts +0 -47
- package/src/hooks/useCommonVariables.test.tsx +0 -384
- package/src/hooks/useInitProps.test.tsx +0 -134
- package/src/hooks/useLayoutConfig.test.tsx +0 -247
- package/src/hooks/useOffsetX.test.ts +0 -54
- package/src/hooks/useOnProgressChange.test.tsx +0 -270
- package/src/hooks/usePanGestureProxy.test.tsx +0 -368
- package/src/hooks/usePropsErrorBoundary.test.tsx +0 -57
- package/src/hooks/useUpdateGestureConfig.test.ts +0 -89
- package/src/hooks/useVisibleRanges.test.tsx +0 -347
- package/src/layouts/parallax.test.ts +0 -239
- package/src/layouts/stack.test.ts +0 -252
- package/src/store/index.test.tsx +0 -314
- package/src/utils/compute-gesture-translation.test.ts +0 -70
- package/src/utils/compute-offset-if-data-changed.test.ts +0 -133
- package/src/utils/compute-offset-if-size-changed.test.ts +0 -78
- package/src/utils/computed-with-auto-fill-data.test.ts +0 -298
- package/src/utils/deal-with-animation.test.ts +0 -181
- package/src/utils/handleroffset-direction.test.ts +0 -124
- package/src/utils/index.test.ts +0 -90
- package/src/utils/log.test.ts +0 -134
- 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
|
-
});
|