Skip to content
This repository has been archived by the owner on Aug 20, 2023. It is now read-only.

Commit

Permalink
fix: Browsers inconsistency in supporting TouchEvent
Browse files Browse the repository at this point in the history
  • Loading branch information
minwork committed Jul 16, 2021
1 parent 858236d commit 76b5d4f
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 44 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "use-long-press",
"version": "1.1.0",
"version": "1.1.1",
"description": "React hook for detecting click (or tap) and hold event. Easy to use, highly customizable options, thoroughly tested.",
"author": "minwork",
"license": "MIT",
Expand Down
12 changes: 6 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
} from 'react';

function isTouchEvent<Target>(event: LongPressEvent<Target>): event is ReactTouchEvent<Target> {
return event.nativeEvent instanceof TouchEvent;
const { nativeEvent } = event;
return window.TouchEvent ? nativeEvent instanceof TouchEvent : 'touches' in nativeEvent;
}
function isMouseEvent<Target>(event: LongPressEvent<Target>): event is ReactMouseEvent<Target> {
return event.nativeEvent instanceof MouseEvent;
Expand Down Expand Up @@ -50,7 +51,6 @@ export enum LongPressDetectEvents {

export type LongPressResult<
Target,
Callback,
DetectType extends LongPressDetectEvents = LongPressDetectEvents.BOTH
> = DetectType extends LongPressDetectEvents.BOTH
? {
Expand Down Expand Up @@ -88,15 +88,15 @@ export interface LongPressOptions<Target = Element> {
onCancel?: LongPressCallback<Target>;
}

export function useLongPress<Target = Element>(callback: null, options?: LongPressOptions<Target>): {};
export function useLongPress<Target = Element>(callback: null, options?: LongPressOptions<Target>): Record<string, never>;
export function useLongPress<Target = Element, Callback extends LongPressCallback<Target> = LongPressCallback<Target>>(
callback: Callback,
options?: LongPressOptions<Target>
): LongPressResult<Target, Callback>;
): LongPressResult<Target>;
export function useLongPress<Target = Element, Callback extends LongPressCallback<Target> = LongPressCallback<Target>>(
callback: Callback | null,
options?: LongPressOptions<Target>
): LongPressResult<Target, Callback> | {};
): LongPressResult<Target> | Record<string, never>;
/**
* Detect click / tap and hold event
*
Expand Down Expand Up @@ -142,7 +142,7 @@ export function useLongPress<
onFinish,
onCancel,
}: LongPressOptions<Target> = {}
): LongPressResult<Target, Callback, typeof detect> | {} {
): LongPressResult<Target, typeof detect> | Record<string, never> {
const isLongPressActive = useRef(false);
const isPressed = useRef(false);
const timer = useRef<NodeJS.Timeout>();
Expand Down
6 changes: 3 additions & 3 deletions tests/TestComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { HTMLAttributes } from 'react';
import React, { Component, HTMLAttributes } from 'react';
import { mount, ReactWrapper, shallow, ShallowWrapper } from 'enzyme';
import { LongPressCallback, LongPressOptions, useLongPress } from '../src';

Expand All @@ -19,11 +19,11 @@ export const TestComponent: React.FC<TestComponentProps> = ({ callback, children
export function createShallowTestComponent<Target = Element>(
props: TestComponentProps
): ShallowWrapper<Required<TestComponentProps & HTMLAttributes<Target>>> {
return shallow(<TestComponent {...props} />);
return shallow<Component<Required<TestComponentProps & HTMLAttributes<Target>>>>(<TestComponent {...props} />);
}

export function createMountedTestComponent<Target = Element>(
props: TestComponentProps
): ReactWrapper<Required<TestComponentProps & HTMLAttributes<Target>>> {
return mount(<TestComponent {...props} />);
return mount<Component<Required<TestComponentProps & HTMLAttributes<Target>>>>(<TestComponent {...props} />);
}
116 changes: 84 additions & 32 deletions tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,68 @@ describe('Check isolated hook calls', () => {
});
});

describe('Browser compatibility', () => {
const originalWindow = { ...window.window };
// let mouseEvent: React.MouseEvent;
let touchEvent: React.TouchEvent;
let windowSpy: jest.MockInstance<typeof window, []>;
let callback: LongPressCallback;
let onStart: LongPressCallback;
let onFinish: LongPressCallback;
let onCancel: LongPressCallback;

beforeEach(() => {
// Use fake timers for detecting long press
jest.useFakeTimers();
// mouseEvent = mockMouseEvent({ persist: jest.fn() });
touchEvent = mockTouchEvent({ persist: jest.fn() });
windowSpy = jest.spyOn(window, 'window', 'get');
callback = jest.fn();
onStart = jest.fn();
onFinish = jest.fn();
onCancel = jest.fn();
});

afterEach(() => {
windowSpy.mockRestore();
jest.clearAllMocks();
jest.clearAllTimers();
});
test('Properly detect TouchEvent event if browser doesnt provide it', () => {
windowSpy.mockImplementation(
() =>
({
...originalWindow,
TouchEvent: undefined,
} as unknown as typeof window)
);

const component = createShallowTestComponent({
callback,
onStart,
onFinish,
onCancel,
captureEvent: true,
detect: LongPressDetectEvents.TOUCH,
});

component.props().onTouchStart(touchEvent);
jest.runOnlyPendingTimers();
component.props().onTouchEnd(touchEvent);

expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith(touchEvent);

expect(onStart).toHaveBeenCalledTimes(1);
expect(onStart).toHaveBeenCalledWith(touchEvent);

expect(onFinish).toHaveBeenCalledTimes(1);
expect(onFinish).toHaveBeenCalledWith(touchEvent);

expect(onCancel).toHaveBeenCalledTimes(0);
});
});

describe('Detect long press and trigger appropriate handlers', () => {
let mouseEvent: React.MouseEvent;
let touchEvent: React.TouchEvent;
Expand Down Expand Up @@ -363,10 +425,10 @@ describe('Check appropriate behaviour considering supplied hook options', () =>
describe('Cancel on movement', () => {
test('Should not cancel on movement when appropriate option is set to false', () => {
const touchEvent = mockTouchEvent({
touches: ([{ pageX: 0, pageY: 0 }] as unknown) as React.TouchList,
touches: [{ pageX: 0, pageY: 0 }] as unknown as React.TouchList,
});
const moveTouchEvent = mockTouchEvent({
touches: ([{ pageX: Number.MAX_SAFE_INTEGER, pageY: Number.MAX_SAFE_INTEGER }] as unknown) as React.TouchList,
touches: [{ pageX: Number.MAX_SAFE_INTEGER, pageY: Number.MAX_SAFE_INTEGER }] as unknown as React.TouchList,
});
const mouseEvent = mockMouseEvent({ pageX: 0, pageY: 0 });
const moveMouseEvent = mockMouseEvent({
Expand Down Expand Up @@ -394,10 +456,10 @@ describe('Check appropriate behaviour considering supplied hook options', () =>

test('Should cancel on movement when appropriate option is set to true', () => {
const touchEvent = mockTouchEvent({
touches: ([{ pageX: 0, pageY: 0 }] as unknown) as React.TouchList,
touches: [{ pageX: 0, pageY: 0 }] as unknown as React.TouchList,
});
const moveTouchEvent = mockTouchEvent({
touches: ([{ pageX: Number.MAX_SAFE_INTEGER, pageY: Number.MAX_SAFE_INTEGER }] as unknown) as React.TouchList,
touches: [{ pageX: Number.MAX_SAFE_INTEGER, pageY: Number.MAX_SAFE_INTEGER }] as unknown as React.TouchList,
});
const mouseEvent = mockMouseEvent({ pageX: 0, pageY: 0 });
const moveMouseEvent = mockMouseEvent({
Expand Down Expand Up @@ -426,10 +488,10 @@ describe('Check appropriate behaviour considering supplied hook options', () =>
test('Should not cancel when within explicitly set movement tolerance', () => {
const tolerance = 10;
const touchEvent = mockTouchEvent({
touches: ([{ pageX: 0, pageY: 0 }] as unknown) as React.TouchList,
touches: [{ pageX: 0, pageY: 0 }] as unknown as React.TouchList,
});
const moveTouchEvent = mockTouchEvent({
touches: ([{ pageX: tolerance, pageY: tolerance }] as unknown) as React.TouchList,
touches: [{ pageX: tolerance, pageY: tolerance }] as unknown as React.TouchList,
});
const mouseEvent = mockMouseEvent({ pageX: 0, pageY: 0 });
const moveMouseEvent = mockMouseEvent({
Expand Down Expand Up @@ -458,10 +520,10 @@ describe('Check appropriate behaviour considering supplied hook options', () =>
test('Should cancel when moved outside explicitly set movement tolerance', () => {
const tolerance = 10;
const touchEvent = mockTouchEvent({
touches: ([{ pageX: 0, pageY: 0 }] as unknown) as React.TouchList,
touches: [{ pageX: 0, pageY: 0 }] as unknown as React.TouchList,
});
const moveTouchEvent = mockTouchEvent({
touches: ([{ pageX: 2 * tolerance, pageY: 2 * tolerance }] as unknown) as React.TouchList,
touches: [{ pageX: 2 * tolerance, pageY: 2 * tolerance }] as unknown as React.TouchList,
});
const mouseEvent = mockMouseEvent({ pageX: 0, pageY: 0 });
const moveMouseEvent = mockMouseEvent({
Expand Down Expand Up @@ -533,11 +595,7 @@ describe('Test general hook behaviour inside a component', () => {
const component = createMountedTestComponent({ callback, threshold, onStart });

// Trigger press start
component
.find('TestComponent')
.children()
.props()
.onMouseDown(mouseEvent);
component.find('TestComponent').children().props().onMouseDown(mouseEvent);

expect(onStart).toHaveBeenCalledTimes(1);

Expand All @@ -563,10 +621,7 @@ describe('Test general hook behaviour inside a component', () => {

const component = createMountedTestComponent({ callback, onStart, onFinish, onCancel });

let props = component
.find('TestComponent')
.children()
.props();
let props = component.find('TestComponent').children().props();

expect(props).toHaveProperty('onMouseDown');
expect(props).toHaveProperty('onMouseUp');
Expand All @@ -581,10 +636,7 @@ describe('Test general hook behaviour inside a component', () => {
});
jest.runOnlyPendingTimers();

props = component
.find('TestComponent')
.children()
.props();
props = component.find('TestComponent').children().props();

expect(props).not.toHaveProperty('onMouseDown');
expect(props).not.toHaveProperty('onMouseUp');
Expand Down Expand Up @@ -618,30 +670,30 @@ describe('Test general hook behaviour inside a component', () => {
const callback = jest.fn();
const component = createShallowTestComponent({ callback, cancelOnMovement: true });

component.props().onMouseDown((fakeEvent as unknown) as React.MouseEvent);
component.props().onMouseDown(fakeEvent as unknown as React.MouseEvent);
jest.runOnlyPendingTimers();
component.props().onMouseUp((fakeEvent as unknown) as React.MouseEvent);
component.props().onMouseUp(fakeEvent as unknown as React.MouseEvent);

expect(callback).toBeCalledTimes(0);

component.props().onMouseDown((fakeEvent as unknown) as React.MouseEvent);
component.props().onMouseMove((fakeEvent as unknown) as React.MouseEvent);
component.props().onMouseDown(fakeEvent as unknown as React.MouseEvent);
component.props().onMouseMove(fakeEvent as unknown as React.MouseEvent);
jest.runOnlyPendingTimers();
component.props().onMouseUp((fakeEvent as unknown) as React.MouseEvent);
component.props().onMouseLeave((fakeEvent as unknown) as React.MouseEvent);
component.props().onMouseUp(fakeEvent as unknown as React.MouseEvent);
component.props().onMouseLeave(fakeEvent as unknown as React.MouseEvent);

expect(callback).toBeCalledTimes(0);

component.props().onTouchStart((fakeEvent as unknown) as React.TouchEvent);
component.props().onTouchStart(fakeEvent as unknown as React.TouchEvent);
jest.runOnlyPendingTimers();
component.props().onTouchEnd((fakeEvent as unknown) as React.TouchEvent);
component.props().onTouchEnd(fakeEvent as unknown as React.TouchEvent);

expect(callback).toBeCalledTimes(0);

component.props().onTouchStart((fakeEvent as unknown) as React.TouchEvent);
component.props().onTouchMove((fakeEvent as unknown) as React.TouchEvent);
component.props().onTouchStart(fakeEvent as unknown as React.TouchEvent);
component.props().onTouchMove(fakeEvent as unknown as React.TouchEvent);
jest.runOnlyPendingTimers();
component.props().onTouchEnd((fakeEvent as unknown) as React.TouchEvent);
component.props().onTouchEnd(fakeEvent as unknown as React.TouchEvent);

expect(callback).toBeCalledTimes(0);
});
Expand Down
4 changes: 2 additions & 2 deletions tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function mockTouchEvent<EventType extends React.TouchEvent = React.TouchE
props?: Partial<EventType>
): EventType {
return {
nativeEvent: new window.TouchEvent('touch'),
nativeEvent: new TouchEvent('touch'),
touches: ([{ pageX: 0, pageY: 0 }] as unknown) as React.TouchList,
...props,
} as EventType;
Expand All @@ -14,7 +14,7 @@ export function mockMouseEvent<EventType extends React.MouseEvent = React.MouseE
props?: Partial<EventType>
): EventType {
return {
nativeEvent: new window.MouseEvent('mouse'),
nativeEvent: new MouseEvent('mouse'),
pageX: 0,
pageY: 0,
...props,
Expand Down

0 comments on commit 76b5d4f

Please sign in to comment.