Skip to content

Commit 4e65b2a

Browse files
committed
refactor(use-long-press): Recognise events by their type instead of class
1 parent 826bd51 commit 4e65b2a

File tree

4 files changed

+118
-30
lines changed

4 files changed

+118
-30
lines changed

packages/use-long-press/src/lib/use-long-press.utils.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,44 @@ import {
66
TouchEvent as ReactTouchEvent,
77
} from 'react';
88

9-
const getPointerEvent = () => (typeof window === 'object' ? window?.PointerEvent ?? null : null);
10-
const getTouchEvent = () => (typeof window === 'object' ? window?.TouchEvent ?? null : null);
9+
const recognisedMouseEvents: string[] = [
10+
'mousedown',
11+
'mousemove',
12+
'mouseup',
13+
'mouseleave',
14+
'mouseout',
15+
] satisfies (keyof WindowEventMap)[];
1116

12-
export function isTouchEvent<Target extends Element>(event: SyntheticEvent<Target>): event is ReactTouchEvent<Target> {
13-
const { nativeEvent } = event;
14-
const TouchEvent = getTouchEvent();
17+
const recognisedTouchEvents: string[] = [
18+
'touchstart',
19+
'touchmove',
20+
'touchend',
21+
'touchcancel',
22+
] satisfies (keyof WindowEventMap)[];
1523

16-
return (TouchEvent && nativeEvent instanceof TouchEvent) || 'touches' in event;
17-
}
24+
const recognisedPointerEvents: string[] = [
25+
'pointerdown',
26+
'pointermove',
27+
'pointerup',
28+
'pointerleave',
29+
'pointerout',
30+
] satisfies (keyof WindowEventMap)[];
1831

1932
export function isMouseEvent<Target extends Element>(event: SyntheticEvent<Target>): event is ReactMouseEvent<Target> {
20-
const PointerEvent = getPointerEvent();
21-
return event.nativeEvent instanceof MouseEvent && !(PointerEvent && event.nativeEvent instanceof PointerEvent);
33+
return recognisedMouseEvents.includes(event?.nativeEvent?.type);
34+
}
35+
36+
export function isTouchEvent<Target extends Element>(event: SyntheticEvent<Target>): event is ReactTouchEvent<Target> {
37+
return recognisedTouchEvents.includes(event?.nativeEvent?.type) || 'touches' in event;
2238
}
2339

2440
export function isPointerEvent<Target extends Element>(
2541
event: SyntheticEvent<Target>
2642
): event is ReactPointerEvent<Target> {
2743
const { nativeEvent } = event;
28-
if (!nativeEvent) {
29-
return false;
30-
}
44+
if (!nativeEvent) return false;
3145

32-
const PointerEvent = getPointerEvent();
33-
return (PointerEvent && nativeEvent instanceof PointerEvent) || 'pointerId' in nativeEvent;
46+
return recognisedPointerEvents.includes(nativeEvent?.type) || 'pointerId' in nativeEvent;
3447
}
3548

3649
export function isRecognisableEvent<Target extends Element>(

packages/use-long-press/src/tests/use-long-press.test.functions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function createMockedTouchEvent<T extends HTMLElement = HTMLElement>(
1919
options?: Partial<ReactTouchEvent<T>> & { nativeEvent?: TouchEvent }
2020
): ReactTouchEvent<T> {
2121
return {
22-
nativeEvent: new TouchEvent('touch'),
22+
nativeEvent: new TouchEvent('touchstart'),
2323
touches: [{ pageX: 0, pageY: 0 }],
2424
...options,
2525
} as ReactTouchEvent<T>;
@@ -29,7 +29,7 @@ export function createMockedMouseEvent<T extends HTMLElement = HTMLElement>(
2929
options?: Partial<ReactMouseEvent<T>> & { nativeEvent?: MouseEvent }
3030
): ReactMouseEvent<T> {
3131
return {
32-
nativeEvent: new MouseEvent('mouse'),
32+
nativeEvent: new MouseEvent('mousedown'),
3333
...options,
3434
} as ReactMouseEvent<T>;
3535
}
@@ -38,7 +38,7 @@ export function createMockedPointerEvent<T extends HTMLElement = HTMLElement>(
3838
options?: Partial<ReactPointerEvent<T>> & { nativeEvent?: PointerEvent }
3939
): ReactPointerEvent<T> {
4040
return {
41-
nativeEvent: new PointerEvent('pointer'),
41+
nativeEvent: new PointerEvent('pointerdown'),
4242
...options,
4343
} as ReactPointerEvent<T>;
4444
}

packages/use-long-press/src/tests/use-long-press.test.tsx

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { act, createEvent, fireEvent, render, renderHook } from '@testing-librar
33
import {
44
LongPressCallback,
55
LongPressCallbackReason,
6+
LongPressDomEvents,
67
LongPressEventType,
78
LongPressMouseHandlers,
89
LongPressOptions,
@@ -11,7 +12,13 @@ import {
1112
LongPressTouchHandlers,
1213
useLongPress,
1314
} from '../lib';
14-
import { isMouseEvent, isPointerEvent, isRecognisableEvent, isTouchEvent } from '../lib/use-long-press.utils';
15+
import {
16+
createArtificialReactEvent,
17+
isMouseEvent,
18+
isPointerEvent,
19+
isRecognisableEvent,
20+
isTouchEvent,
21+
} from '../lib/use-long-press.utils';
1522
import {
1623
createTestComponent,
1724
createTestElement,
@@ -650,6 +657,57 @@ describe('Hook options', () => {
650657
expect(onFinish).toHaveBeenCalledTimes(1);
651658
}
652659
);
660+
661+
test.each([[LongPressEventType.Mouse], [LongPressEventType.Touch], [LongPressEventType.Pointer]])(
662+
'Trigger cancel on window when moved "%s" outside element if option is set to false',
663+
(eventType) => {
664+
const callback = vi.fn();
665+
const onCancel = vi.fn();
666+
const onFinish = vi.fn();
667+
const windowOnCancel = vi.fn();
668+
const windowEventName = {
669+
[LongPressEventType.Mouse]: 'mouseup',
670+
[LongPressEventType.Touch]: 'touchend',
671+
[LongPressEventType.Pointer]: 'pointerup',
672+
}[eventType];
673+
const threshold = 1000;
674+
675+
const element = createTestElement({
676+
callback,
677+
onCancel,
678+
onFinish,
679+
detect: eventType,
680+
cancelOutsideElement: false,
681+
threshold,
682+
});
683+
const longPressEvent = getDOMTestHandlersMap(eventType, element);
684+
const event = longPressMockedEventCreatorMap[eventType]();
685+
686+
window.addEventListener(windowEventName, windowOnCancel);
687+
688+
longPressEvent.start(event);
689+
longPressEvent.leave?.(event);
690+
expect(callback).toHaveBeenCalledTimes(0);
691+
expect(onFinish).toHaveBeenCalledTimes(0);
692+
expect(onCancel).toHaveBeenCalledTimes(0);
693+
694+
vi.advanceTimersByTime(threshold + 1);
695+
696+
expect(callback).toHaveBeenCalledTimes(1);
697+
expect(onFinish).toHaveBeenCalledTimes(0);
698+
expect(onCancel).toHaveBeenCalledTimes(0);
699+
expect(windowOnCancel).toHaveBeenCalledTimes(0);
700+
701+
fireEvent(window, createEvent(windowEventName, window));
702+
703+
expect(windowOnCancel).toHaveBeenCalledTimes(1);
704+
expect(callback).toHaveBeenCalledTimes(1);
705+
expect(onFinish).toHaveBeenCalledTimes(1);
706+
expect(onCancel).toHaveBeenCalledTimes(0);
707+
708+
window.removeEventListener(windowEventName, windowOnCancel);
709+
}
710+
);
653711
});
654712

655713
describe('filterEvents', () => {
@@ -1361,4 +1419,17 @@ describe('Utils', () => {
13611419
])('isPointerEvent treat %s as pointer event: %s', (event, isProperEvent) => {
13621420
expect(isPointerEvent(event as LongPressReactEvents)).toBe(isProperEvent);
13631421
});
1422+
1423+
test.each([
1424+
['mouseDown' as const],
1425+
['mouseUp' as const],
1426+
['touchStart' as const],
1427+
['touchEnd' as const],
1428+
['pointerDown' as const],
1429+
['pointerUp' as const],
1430+
])('Create recognisable artificial %s react event', (eventName) => {
1431+
const event = createEvent[eventName](window);
1432+
const reactEvent = createArtificialReactEvent(event as LongPressDomEvents);
1433+
expect(isRecognisableEvent(reactEvent)).toBe(true);
1434+
});
13641435
});

packages/use-long-press/src/tests/use-long-press.test.utils.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent, TouchEvent as ReactTouchEvent } from 'react';
2-
import { createEvent, fireEvent } from '@testing-library/react';
2+
import { createEvent, fireEvent, FireObject } from '@testing-library/react';
33
import { EventType } from '@testing-library/dom/types/events';
44
import { LongPressDomEvents, LongPressEventType, LongPressHandlers } from '../lib';
55
import {
@@ -64,26 +64,30 @@ export function getDOMTestHandlersMap(
6464
eventType: LongPressEventType,
6565
element: Window | Element | Node | Document
6666
): LongPressTestHandlersMap {
67+
function eventHandler(eventName: keyof FireObject) {
68+
return (options?: object) => fireEvent[eventName](element, options);
69+
}
70+
6771
switch (eventType) {
6872
case LongPressEventType.Mouse:
6973
return {
70-
start: fireEvent.mouseDown.bind(null, element),
71-
move: fireEvent.mouseMove.bind(null, element),
72-
stop: fireEvent.mouseUp.bind(null, element),
73-
leave: fireEvent.mouseLeave.bind(null, element),
74+
start: eventHandler('mouseDown'),
75+
move: eventHandler('mouseMove'),
76+
stop: eventHandler('mouseUp'),
77+
leave: eventHandler('mouseLeave'),
7478
};
7579
case LongPressEventType.Touch:
7680
return {
77-
start: fireEvent.touchStart.bind(null, element),
78-
move: fireEvent.touchMove.bind(null, element),
79-
stop: fireEvent.touchEnd.bind(null, element),
81+
start: eventHandler('touchStart'),
82+
move: eventHandler('touchMove'),
83+
stop: eventHandler('touchEnd'),
8084
};
8185
case LongPressEventType.Pointer: {
8286
return {
83-
start: fireEvent.pointerDown.bind(null, element),
84-
move: fireEvent.pointerMove.bind(null, element),
85-
stop: fireEvent.pointerUp.bind(null, element),
86-
leave: fireEvent.pointerLeave.bind(null, element),
87+
start: eventHandler('pointerDown'),
88+
move: eventHandler('pointerMove'),
89+
stop: eventHandler('pointerUp'),
90+
leave: eventHandler('pointerLeave'),
8791
};
8892
}
8993
}

0 commit comments

Comments
 (0)