Skip to content

Commit a2845f1

Browse files
fix(app): ignore a double tap on a photo that did not complete (#480)
Gesture handler calls onEnd for failed taps too, so a double tap that drifted or lost to a pinch still zoomed in, now on the spot where it was abandoned.
1 parent cc5ec26 commit a2845f1

2 files changed

Lines changed: 32 additions & 4 deletions

File tree

‎app/src/components/product/ZoomableImage.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,8 @@ export default function ZoomableImage({
195195
// tap between, any part of the photo can be reached without a drag (WCAG 2.5.7).
196196
const doubleTapGesture = Gesture.Tap()
197197
.numberOfTaps(2)
198-
.onEnd((e) => {
198+
.onEnd((e, success) => {
199+
if (!success) return;
199200
if (scale.get() > 1.1) {
200201
resetZoom();
201202
} else {

‎app/src/components/product/__tests__/ZoomableImage.test.tsx‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ describe('ZoomableImage', () => {
274274

275275
const onEnd = mockTap.onEnd.mock.calls[0][0];
276276
await act(() => {
277-
onEnd({ x: 0, y: 0 });
277+
onEnd({ x: 0, y: 0 }, true);
278278
});
279279

280280
expect(onScaleChange).toHaveBeenLastCalledWith(2);
@@ -309,13 +309,40 @@ describe('ZoomableImage', () => {
309309
});
310310
const onEnd = mockTap.onEnd.mock.calls[0][0];
311311
await act(() => {
312-
onEnd({ x: 10, y: 70 });
312+
onEnd({ x: 10, y: 70 }, true);
313313
});
314314

315315
const { transform } = StyleSheet.flatten(screen.getByTestId('zoomable-image').props.style);
316316
expect(transform).toEqual([{ translateX: 40 }, { translateY: -30 }, { scale: 2 }]);
317317
});
318318

319+
// A double tap that fails (the finger drifted, or a pinch took over) must not zoom.
320+
it('ignores a double tap that did not complete', async () => {
321+
const mockTap = {
322+
numberOfTaps: jest.fn().mockReturnThis(),
323+
onEnd: jest.fn().mockReturnThis(),
324+
};
325+
jest.spyOn(Gesture, 'Tap').mockReturnValue(mockTap as unknown as TapGestureType);
326+
jest.spyOn(Reanimated, 'useSharedValue').mockImplementation((initial) => {
327+
let value = initial;
328+
return useRef({
329+
get: () => value,
330+
set: (next: typeof initial) => {
331+
value = next;
332+
},
333+
}).current as unknown as ReturnType<typeof Reanimated.useSharedValue>;
334+
});
335+
336+
await render(<ZoomableImage uri={testUri} />);
337+
const onEnd = mockTap.onEnd.mock.calls[0][0];
338+
await act(() => {
339+
onEnd({ x: 10, y: 70 }, false);
340+
});
341+
342+
const { transform } = StyleSheet.flatten(screen.getByTestId('zoomable-image').props.style);
343+
expect(transform).toEqual([{ translateX: 0 }, { translateY: 0 }, { scale: 1 }]);
344+
});
345+
319346
it('resets a zoomed image when double tapped again', async () => {
320347
const mockPinch = {
321348
onUpdate: jest.fn().mockReturnThis(),
@@ -343,7 +370,7 @@ describe('ZoomableImage', () => {
343370
await act(() => {
344371
pinchUpdate({ scale: 2 });
345372
pinchEnd();
346-
onEnd({ x: 0, y: 0 });
373+
onEnd({ x: 0, y: 0 }, true);
347374
});
348375

349376
expect(onScaleChange).toHaveBeenLastCalledWith(1);

0 commit comments

Comments
 (0)