Skip to content

Commit

Permalink
refactor(use-long-press): Recognise events by their type instead of c…
Browse files Browse the repository at this point in the history
…lass
  • Loading branch information
minwork committed Dec 8, 2023
1 parent 826bd51 commit 4e65b2a
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 30 deletions.
41 changes: 27 additions & 14 deletions packages/use-long-press/src/lib/use-long-press.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,44 @@ import {
TouchEvent as ReactTouchEvent,
} from 'react';

const getPointerEvent = () => (typeof window === 'object' ? window?.PointerEvent ?? null : null);
const getTouchEvent = () => (typeof window === 'object' ? window?.TouchEvent ?? null : null);
const recognisedMouseEvents: string[] = [
'mousedown',
'mousemove',
'mouseup',
'mouseleave',
'mouseout',
] satisfies (keyof WindowEventMap)[];

export function isTouchEvent<Target extends Element>(event: SyntheticEvent<Target>): event is ReactTouchEvent<Target> {
const { nativeEvent } = event;
const TouchEvent = getTouchEvent();
const recognisedTouchEvents: string[] = [
'touchstart',
'touchmove',
'touchend',
'touchcancel',
] satisfies (keyof WindowEventMap)[];

return (TouchEvent && nativeEvent instanceof TouchEvent) || 'touches' in event;
}
const recognisedPointerEvents: string[] = [
'pointerdown',
'pointermove',
'pointerup',
'pointerleave',
'pointerout',
] satisfies (keyof WindowEventMap)[];

export function isMouseEvent<Target extends Element>(event: SyntheticEvent<Target>): event is ReactMouseEvent<Target> {
const PointerEvent = getPointerEvent();
return event.nativeEvent instanceof MouseEvent && !(PointerEvent && event.nativeEvent instanceof PointerEvent);
return recognisedMouseEvents.includes(event?.nativeEvent?.type);
}

export function isTouchEvent<Target extends Element>(event: SyntheticEvent<Target>): event is ReactTouchEvent<Target> {
return recognisedTouchEvents.includes(event?.nativeEvent?.type) || 'touches' in event;
}

export function isPointerEvent<Target extends Element>(
event: SyntheticEvent<Target>
): event is ReactPointerEvent<Target> {
const { nativeEvent } = event;
if (!nativeEvent) {
return false;
}
if (!nativeEvent) return false;

const PointerEvent = getPointerEvent();
return (PointerEvent && nativeEvent instanceof PointerEvent) || 'pointerId' in nativeEvent;
return recognisedPointerEvents.includes(nativeEvent?.type) || 'pointerId' in nativeEvent;
}

export function isRecognisableEvent<Target extends Element>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function createMockedTouchEvent<T extends HTMLElement = HTMLElement>(
options?: Partial<ReactTouchEvent<T>> & { nativeEvent?: TouchEvent }
): ReactTouchEvent<T> {
return {
nativeEvent: new TouchEvent('touch'),
nativeEvent: new TouchEvent('touchstart'),
touches: [{ pageX: 0, pageY: 0 }],
...options,
} as ReactTouchEvent<T>;
Expand All @@ -29,7 +29,7 @@ export function createMockedMouseEvent<T extends HTMLElement = HTMLElement>(
options?: Partial<ReactMouseEvent<T>> & { nativeEvent?: MouseEvent }
): ReactMouseEvent<T> {
return {
nativeEvent: new MouseEvent('mouse'),
nativeEvent: new MouseEvent('mousedown'),
...options,
} as ReactMouseEvent<T>;
}
Expand All @@ -38,7 +38,7 @@ export function createMockedPointerEvent<T extends HTMLElement = HTMLElement>(
options?: Partial<ReactPointerEvent<T>> & { nativeEvent?: PointerEvent }
): ReactPointerEvent<T> {
return {
nativeEvent: new PointerEvent('pointer'),
nativeEvent: new PointerEvent('pointerdown'),
...options,
} as ReactPointerEvent<T>;
}
Expand Down
73 changes: 72 additions & 1 deletion packages/use-long-press/src/tests/use-long-press.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { act, createEvent, fireEvent, render, renderHook } from '@testing-librar
import {
LongPressCallback,
LongPressCallbackReason,
LongPressDomEvents,
LongPressEventType,
LongPressMouseHandlers,
LongPressOptions,
Expand All @@ -11,7 +12,13 @@ import {
LongPressTouchHandlers,
useLongPress,
} from '../lib';
import { isMouseEvent, isPointerEvent, isRecognisableEvent, isTouchEvent } from '../lib/use-long-press.utils';
import {
createArtificialReactEvent,
isMouseEvent,
isPointerEvent,
isRecognisableEvent,
isTouchEvent,
} from '../lib/use-long-press.utils';
import {
createTestComponent,
createTestElement,
Expand Down Expand Up @@ -650,6 +657,57 @@ describe('Hook options', () => {
expect(onFinish).toHaveBeenCalledTimes(1);
}
);

test.each([[LongPressEventType.Mouse], [LongPressEventType.Touch], [LongPressEventType.Pointer]])(
'Trigger cancel on window when moved "%s" outside element if option is set to false',
(eventType) => {
const callback = vi.fn();
const onCancel = vi.fn();
const onFinish = vi.fn();
const windowOnCancel = vi.fn();
const windowEventName = {
[LongPressEventType.Mouse]: 'mouseup',
[LongPressEventType.Touch]: 'touchend',
[LongPressEventType.Pointer]: 'pointerup',
}[eventType];
const threshold = 1000;

const element = createTestElement({
callback,
onCancel,
onFinish,
detect: eventType,
cancelOutsideElement: false,
threshold,
});
const longPressEvent = getDOMTestHandlersMap(eventType, element);
const event = longPressMockedEventCreatorMap[eventType]();

window.addEventListener(windowEventName, windowOnCancel);

longPressEvent.start(event);
longPressEvent.leave?.(event);
expect(callback).toHaveBeenCalledTimes(0);
expect(onFinish).toHaveBeenCalledTimes(0);
expect(onCancel).toHaveBeenCalledTimes(0);

vi.advanceTimersByTime(threshold + 1);

expect(callback).toHaveBeenCalledTimes(1);
expect(onFinish).toHaveBeenCalledTimes(0);
expect(onCancel).toHaveBeenCalledTimes(0);
expect(windowOnCancel).toHaveBeenCalledTimes(0);

fireEvent(window, createEvent(windowEventName, window));

expect(windowOnCancel).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledTimes(1);
expect(onFinish).toHaveBeenCalledTimes(1);
expect(onCancel).toHaveBeenCalledTimes(0);

window.removeEventListener(windowEventName, windowOnCancel);
}
);
});

describe('filterEvents', () => {
Expand Down Expand Up @@ -1361,4 +1419,17 @@ describe('Utils', () => {
])('isPointerEvent treat %s as pointer event: %s', (event, isProperEvent) => {
expect(isPointerEvent(event as LongPressReactEvents)).toBe(isProperEvent);
});

test.each([
['mouseDown' as const],
['mouseUp' as const],
['touchStart' as const],
['touchEnd' as const],
['pointerDown' as const],
['pointerUp' as const],
])('Create recognisable artificial %s react event', (eventName) => {
const event = createEvent[eventName](window);
const reactEvent = createArtificialReactEvent(event as LongPressDomEvents);
expect(isRecognisableEvent(reactEvent)).toBe(true);
});
});
28 changes: 16 additions & 12 deletions packages/use-long-press/src/tests/use-long-press.test.utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent, TouchEvent as ReactTouchEvent } from 'react';
import { createEvent, fireEvent } from '@testing-library/react';
import { createEvent, fireEvent, FireObject } from '@testing-library/react';
import { EventType } from '@testing-library/dom/types/events';
import { LongPressDomEvents, LongPressEventType, LongPressHandlers } from '../lib';
import {
Expand Down Expand Up @@ -64,26 +64,30 @@ export function getDOMTestHandlersMap(
eventType: LongPressEventType,
element: Window | Element | Node | Document
): LongPressTestHandlersMap {
function eventHandler(eventName: keyof FireObject) {
return (options?: object) => fireEvent[eventName](element, options);
}

switch (eventType) {
case LongPressEventType.Mouse:
return {
start: fireEvent.mouseDown.bind(null, element),
move: fireEvent.mouseMove.bind(null, element),
stop: fireEvent.mouseUp.bind(null, element),
leave: fireEvent.mouseLeave.bind(null, element),
start: eventHandler('mouseDown'),
move: eventHandler('mouseMove'),
stop: eventHandler('mouseUp'),
leave: eventHandler('mouseLeave'),
};
case LongPressEventType.Touch:
return {
start: fireEvent.touchStart.bind(null, element),
move: fireEvent.touchMove.bind(null, element),
stop: fireEvent.touchEnd.bind(null, element),
start: eventHandler('touchStart'),
move: eventHandler('touchMove'),
stop: eventHandler('touchEnd'),
};
case LongPressEventType.Pointer: {
return {
start: fireEvent.pointerDown.bind(null, element),
move: fireEvent.pointerMove.bind(null, element),
stop: fireEvent.pointerUp.bind(null, element),
leave: fireEvent.pointerLeave.bind(null, element),
start: eventHandler('pointerDown'),
move: eventHandler('pointerMove'),
stop: eventHandler('pointerUp'),
leave: eventHandler('pointerLeave'),
};
}
}
Expand Down

0 comments on commit 4e65b2a

Please sign in to comment.