Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions e2e/11-display-toggle-regression.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ name: Display Toggle Gesture Regression
# Navigate to E2E test page and reset defaults
- runFlow: helpers/navigate-to-e2e.yaml
# Baseline swipe: ensure gestures work before toggling display
- swipe:
start: "85%, 22%"
end: "15%, 22%"
duration: 500

- waitForAnimationToEnd:
timeout: 3000
- repeat:
while:
notVisible: "Current Index: 1"
times: 3
commands:
- tapOn:
id: "e2e-carousel"
- swipe:
start: "85%, 22%"
end: "15%, 22%"
duration: 500
- waitForAnimationToEnd:
timeout: 3000

- assertVisible: "Current Index: 1"

Expand All @@ -33,12 +39,18 @@ name: Display Toggle Gesture Regression
- assertVisible: "Display: flex"

# If issue exists, this swipe won't change index anymore
- swipe:
start: "85%, 22%"
end: "15%, 22%"
duration: 500

- waitForAnimationToEnd:
timeout: 3000
- repeat:
while:
notVisible: "Current Index: 2"
times: 3
commands:
- tapOn:
id: "e2e-carousel"
- swipe:
start: "85%, 22%"
end: "15%, 22%"
duration: 500
- waitForAnimationToEnd:
timeout: 3000

- assertVisible: "Current Index: 2"
39 changes: 39 additions & 0 deletions src/components/Carousel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,45 @@ describe("Test the real swipe behavior of Carousel to ensure it's working as exp
}
});

it("display toggle regression #885: should keep swipe working after hide/show layout cycle", async () => {
const progress = { current: 0 };
const Wrapper = createCarousel(progress);
const { getByTestId } = render(<Wrapper style={{ width: slideWidth, height: slideHeight }} />);
await verifyInitialRender(getByTestId);

const initialPanCount = mockPan.mock.calls.length;
expect(initialPanCount).toBeGreaterThan(0);

swipeToLeftOnce();
await waitFor(() => expect(progress.current).toBe(1));

const contentContainer = getByTestId("carousel-content-container");
expect(typeof contentContainer.props.onLayout).toBe("function");

act(() => {
contentContainer.props.onLayout?.({
nativeEvent: { layout: { width: 0, height: 0 } },
} as any);
});

act(() => {
contentContainer.props.onLayout?.({
nativeEvent: { layout: { width: slideWidth, height: slideHeight } },
} as any);
});

act(() => {
jest.runOnlyPendingTimers();
});

await waitFor(() => {
expect(mockPan.mock.calls.length).toBeGreaterThan(initialPanCount);
});

swipeToLeftOnce();
await waitFor(() => expect(progress.current).toBe(2));
});

it("`loop` prop: should swipe back to the first item when loop is true", async () => {
const progress = { current: 0 };
const Wrapper = createCarousel(progress);
Expand Down
29 changes: 27 additions & 2 deletions src/components/ScrollViewGesture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useCallback } from "react";
import type { LayoutChangeEvent, StyleProp, ViewStyle } from "react-native";
import type {
GestureStateChangeEvent,
PanGesture,
PanGestureHandlerEventPayload,
} from "react-native-gesture-handler";
import { GestureDetector } from "react-native-gesture-handler";
Expand Down Expand Up @@ -70,13 +71,15 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
} = props;

const maxPage = dataLength;
const [gestureEpoch, bumpGestureEpoch] = React.useReducer((value: number) => value + 1, 0);
const isHorizontal = useDerivedValue(() => !vertical, [vertical]);
const max = useSharedValue(0);
const panOffset = useSharedValue<number | undefined>(undefined); // set to undefined when not actively in a pan gesture
const touching = useSharedValue(false);
const validStart = useSharedValue(false);
const scrollEndTranslation = useSharedValue(0);
const scrollEndVelocity = useSharedValue(0);
const layoutWasHidden = useSharedValue(false);
const containerRef = useAnimatedRef<Animated.View>();
const maxScrollDistancePerSwipeIsSet = typeof maxScrollDistancePerSwipe === "number";
const minScrollDistancePerSwipeIsSet = typeof minScrollDistancePerSwipe === "number";
Expand Down Expand Up @@ -479,8 +482,15 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
]
);

const onConfigurePanGestureProxy = React.useCallback(
(gesture: PanGesture) => {
onConfigurePanGesture?.(gesture);
},
[onConfigurePanGesture, gestureEpoch]
);

const gesture = usePanGestureProxy({
onConfigurePanGesture,
onConfigurePanGesture: onConfigurePanGestureProxy,
onGestureStart,
onGestureUpdate,
onGestureEnd,
Expand All @@ -495,6 +505,13 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
const measuredHeight = e.nativeEvent.layout.height;
const measuredSize = Math.round((vertical ? measuredHeight : measuredWidth) || 0);

if (measuredSize <= 0) {
layoutWasHidden.value = true;
} else if (layoutWasHidden.value) {
layoutWasHidden.value = false;
scheduleOnRN(bumpGestureEpoch);
}

if (!sizeExplicit && measuredSize > 0) {
const current = resolvedSize.value ?? 0;
if (Math.abs(current - measuredSize) > 0) {
Expand All @@ -509,7 +526,15 @@ const IScrollViewGesture: React.FC<PropsWithChildren<Props>> = (props) => {
height: measuredHeight,
});
},
[updateContainerSize, resolvedSize, sizePhase, vertical, sizeExplicit]
[
updateContainerSize,
resolvedSize,
sizePhase,
vertical,
sizeExplicit,
layoutWasHidden,
bumpGestureEpoch,
]
);

return (
Expand Down
Loading