Skip to content

Commit c24449b

Browse files
committed
feat: testOnly event handlers
1 parent 315afca commit c24449b

File tree

4 files changed

+99
-32
lines changed

4 files changed

+99
-32
lines changed

src/__tests__/event-handler.test.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as React from 'react';
2+
import { Text, View } from 'react-native';
3+
import { render, screen } from '../..';
4+
import { getEventHandler } from '../event-handler';
5+
6+
test('getEventHandler strict mode', () => {
7+
const onPress = jest.fn();
8+
const testOnlyOnPress = jest.fn();
9+
10+
render(
11+
<View>
12+
<Text testID="regular" onPress={onPress} />
13+
{/* @ts-expect-error Intentionally passing such props */}
14+
<View testID="testOnly" testOnly_onPress={testOnlyOnPress} />
15+
{/* @ts-expect-error Intentionally passing such props */}
16+
<View testID="both" onPress={onPress} testOnly_onPress={testOnlyOnPress} />
17+
</View>,
18+
);
19+
20+
const regular = screen.getByTestId('regular');
21+
const testOnly = screen.getByTestId('testOnly');
22+
const both = screen.getByTestId('both');
23+
24+
expect(getEventHandler(regular, 'press')).toBe(onPress);
25+
expect(getEventHandler(testOnly, 'press')).toBe(testOnlyOnPress);
26+
expect(getEventHandler(both, 'press')).toBe(onPress);
27+
28+
expect(getEventHandler(regular, 'onPress')).toBe(undefined);
29+
expect(getEventHandler(testOnly, 'onPress')).toBe(undefined);
30+
expect(getEventHandler(both, 'onPress')).toBe(undefined);
31+
});
32+
33+
test('getEventHandler loose mode', () => {
34+
const onPress = jest.fn();
35+
const testOnlyOnPress = jest.fn();
36+
37+
render(
38+
<View>
39+
<Text testID="regular" onPress={onPress} />
40+
{/* @ts-expect-error Intentionally passing such props */}
41+
<View testID="testOnly" testOnly_onPress={testOnlyOnPress} />
42+
{/* @ts-expect-error Intentionally passing such props */}
43+
<View testID="both" onPress={onPress} testOnly_onPress={testOnlyOnPress} />
44+
</View>,
45+
);
46+
47+
const regular = screen.getByTestId('regular');
48+
const testOnly = screen.getByTestId('testOnly');
49+
const both = screen.getByTestId('both');
50+
51+
expect(getEventHandler(regular, 'press', { loose: true })).toBe(onPress);
52+
expect(getEventHandler(testOnly, 'press', { loose: true })).toBe(testOnlyOnPress);
53+
expect(getEventHandler(both, 'press', { loose: true })).toBe(onPress);
54+
55+
expect(getEventHandler(regular, 'onPress', { loose: true })).toBe(onPress);
56+
expect(getEventHandler(testOnly, 'onPress', { loose: true })).toBe(testOnlyOnPress);
57+
expect(getEventHandler(both, 'onPress', { loose: true })).toBe(onPress);
58+
});

src/event-handler.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { ReactTestInstance } from 'react-test-renderer';
2+
3+
export type EventHandlerOptions = {
4+
loose?: boolean;
5+
};
6+
7+
export function getEventHandler(
8+
element: ReactTestInstance,
9+
eventName: string,
10+
options?: EventHandlerOptions,
11+
) {
12+
const handlerName = getEventHandlerName(eventName);
13+
if (typeof element.props[handlerName] === 'function') {
14+
return element.props[handlerName];
15+
}
16+
17+
if (options?.loose && typeof element.props[eventName] === 'function') {
18+
return element.props[eventName];
19+
}
20+
21+
if (typeof element.props[`testOnly_${handlerName}`] === 'function') {
22+
return element.props[`testOnly_${handlerName}`];
23+
}
24+
25+
if (options?.loose && typeof element.props[`testOnly_${eventName}`] === 'function') {
26+
return element.props[`testOnly_${eventName}`];
27+
}
28+
29+
return undefined;
30+
}
31+
32+
export function getEventHandlerName(eventName: string) {
33+
return `on${capitalizeFirstLetter(eventName)}`;
34+
}
35+
36+
function capitalizeFirstLetter(str: string) {
37+
return str.charAt(0).toUpperCase() + str.slice(1);
38+
}

src/fire-event.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
import type { ReactTestInstance } from 'react-test-renderer';
99

1010
import act from './act';
11+
import { getEventHandler } from './event-handler';
1112
import { isElementMounted, isHostElement } from './helpers/component-tree';
1213
import { isHostScrollView, isHostTextInput } from './helpers/host-component-names';
1314
import { isPointerEventEnabled } from './helpers/pointer-events';
@@ -80,7 +81,7 @@ function findEventHandler(
8081
): EventHandler | null {
8182
const touchResponder = isTouchResponder(element) ? element : nearestTouchResponder;
8283

83-
const handler = getEventHandler(element, eventName);
84+
const handler = getEventHandler(element, eventName, { loose: true });
8485
if (handler && isEventEnabled(element, eventName, touchResponder)) return handler;
8586

8687
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
@@ -91,23 +92,6 @@ function findEventHandler(
9192
return findEventHandler(element.parent, eventName, touchResponder);
9293
}
9394

94-
function getEventHandler(element: ReactTestInstance, eventName: string) {
95-
const eventHandlerName = getEventHandlerName(eventName);
96-
if (typeof element.props[eventHandlerName] === 'function') {
97-
return element.props[eventHandlerName];
98-
}
99-
100-
if (typeof element.props[eventName] === 'function') {
101-
return element.props[eventName];
102-
}
103-
104-
return undefined;
105-
}
106-
107-
function getEventHandlerName(eventName: string) {
108-
return `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`;
109-
}
110-
11195
// String union type of keys of T that start with on, stripped of 'on'
11296
type EventNameExtractor<T> = keyof {
11397
[K in keyof T as K extends `on${infer Rest}` ? Uncapitalize<Rest> : never]: T[K];

src/user-event/utils/dispatch-event.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ReactTestInstance } from 'react-test-renderer';
22

33
import act from '../../act';
4+
import { getEventHandler } from '../../event-handler';
45
import { isElementMounted } from '../../helpers/component-tree';
56

67
/**
@@ -25,17 +26,3 @@ export function dispatchEvent(element: ReactTestInstance, eventName: string, ...
2526
handler(...event);
2627
});
2728
}
28-
29-
function getEventHandler(element: ReactTestInstance, eventName: string) {
30-
const handleName = getEventHandlerName(eventName);
31-
const handle = element.props[handleName] as unknown;
32-
if (typeof handle !== 'function') {
33-
return undefined;
34-
}
35-
36-
return handle;
37-
}
38-
39-
function getEventHandlerName(eventName: string) {
40-
return `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`;
41-
}

0 commit comments

Comments
 (0)